@@ -135,26 +135,23 @@ export async function requestEnsureAuthenticated(params: CommonParams, httpOptio
135135// Attempts to get the Basic auth credentials for the provided registry.
136136// This credential is used to offer the registry in exchange for a Bearer token.
137137// These may be:
138- // - Crafted from the GITHUB_TOKEN environment variables
139138// - parsed out of a special DEVCONTAINERS_OCI_AUTH environment variable
140139// - Read from a docker config file
140+ // - Read from a docker credential helper (https://docs.docker.com/engine/reference/commandline/login/#credentials-store)
141+ // - Crafted from the GITHUB_TOKEN environment variable
141142// Returns:
142143// - undefined: No credential was found.
143144// - object: A credential was found.
144- // - based64EncodedCredential: The credential, base64 encoded .
145+ // - based64EncodedCredential: The base64 encoded credential, if any .
145146// - refreshToken: The refresh token, if any.
146147async function getCredential ( params : CommonParams , ociRef : OCIRef | OCICollectionRef ) : Promise < { base64EncodedCredential : string | undefined ; refreshToken : string | undefined } | undefined > {
147148 const { output, env } = params ;
148149 const { registry } = ociRef ;
149150
150- if ( ! ! env [ 'GITHUB_TOKEN' ] && registry === 'ghcr.io' ) {
151- output . write ( '[httpOci] Using environment GITHUB_TOKEN for auth' , LogLevel . Trace ) ;
152- const userToken = `USERNAME:${ env [ 'GITHUB_TOKEN' ] } ` ;
153- return {
154- base64EncodedCredential : Buffer . from ( userToken ) . toString ( 'base64' ) ,
155- refreshToken : undefined ,
156- } ;
157- } else if ( ! ! env [ 'DEVCONTAINERS_OCI_AUTH' ] ) {
151+ const githubToken = env [ 'GITHUB_TOKEN' ] ;
152+ const githubHost = env [ 'GITHUB_HOST' ] ;
153+
154+ if ( ! ! env [ 'DEVCONTAINERS_OCI_AUTH' ] ) {
158155 // eg: DEVCONTAINERS_OCI_AUTH=service1|user1|token1,service2|user2|token2
159156 const authContexts = env [ 'DEVCONTAINERS_OCI_AUTH' ] . split ( ',' ) ;
160157 const authContext = authContexts . find ( a => a . split ( '|' ) [ 0 ] === registry ) ;
@@ -168,76 +165,21 @@ async function getCredential(params: CommonParams, ociRef: OCIRef | OCICollectio
168165 refreshToken : undefined ,
169166 } ;
170167 }
171- } else {
172- let configContainsAuth = false ;
173- try {
174- const homeDir = os . homedir ( ) ;
175- if ( homeDir ) {
176- const dockerConfigPath = path . join ( homeDir , '.docker' , 'config.json' ) ;
177- if ( await isLocalFile ( dockerConfigPath ) ) {
178- const dockerConfig : DockerConfigFile = jsonc . parse ( ( await readLocalFile ( dockerConfigPath ) ) . toString ( ) ) ;
179-
180- configContainsAuth = Object . keys ( dockerConfig . credHelpers ) . length > 0 || ! ! dockerConfig . credsStore || Object . keys ( dockerConfig . auths ) . length > 0 ;
181- if ( dockerConfig . credHelpers && dockerConfig . credHelpers [ registry ] ) {
182- const credHelper = dockerConfig . credHelpers [ registry ] ;
183- output . write ( `[httpOci] Found credential helper '${ credHelper } ' in '${ dockerConfigPath } ' registry '${ registry } '` , LogLevel . Trace ) ;
184- const auth = await getCredentialFromHelper ( params , registry , credHelper ) ;
185- if ( auth ) {
186- return auth ;
187- }
188- } else if ( dockerConfig . credsStore ) {
189- output . write ( `[httpOci] Invoking credsStore credential helper '${ dockerConfig . credsStore } '` , LogLevel . Trace ) ;
190- const auth = await getCredentialFromHelper ( params , registry , dockerConfig . credsStore ) ;
191- if ( auth ) {
192- return auth ;
193- }
194- }
195- if ( dockerConfig . auths && dockerConfig . auths [ registry ] ) {
196- output . write ( `[httpOci] Found auths entry in '${ dockerConfigPath } ' for registry '${ registry } '` , LogLevel . Trace ) ;
197- const auth = dockerConfig . auths [ registry ] . auth ;
198- const identityToken = dockerConfig . auths [ registry ] . identitytoken ; // Refresh token, seen when running: 'az acr login -n <registry>'
199-
200- if ( identityToken ) {
201- return {
202- refreshToken : identityToken ,
203- base64EncodedCredential : undefined ,
204- } ;
205- }
206-
207- // Without the presence of an `identityToken`, assume auth is a base64-encoded 'user:token'.
208- return {
209- base64EncodedCredential : auth ,
210- refreshToken : undefined ,
211- } ;
212- }
213- }
214- }
215- } catch ( err ) {
216- output . write ( `[httpOci] Failed to read docker config.json: ${ err } ` , LogLevel . Trace ) ;
217- }
168+ }
218169
219- if ( ! configContainsAuth ) {
220- let defaultCredHelper = '' ;
221- // Try platform-specific default credential helper
222- if ( process . platform === 'linux' ) {
223- if ( await existsInPath ( 'pass' ) ) {
224- defaultCredHelper = 'pass' ;
225- } else {
226- defaultCredHelper = 'secret' ;
227- }
228- } else if ( process . platform === 'win32' ) {
229- defaultCredHelper = 'wincred' ;
230- } else if ( process . platform === 'darwin' ) {
231- defaultCredHelper = 'osxkeychain' ;
232- }
233- if ( defaultCredHelper !== '' ) {
234- output . write ( `[httpOci] Invoking platform default credential helper '${ defaultCredHelper } '` , LogLevel . Trace ) ;
235- const auth = await getCredentialFromHelper ( params , registry , defaultCredHelper ) ;
236- if ( auth ) {
237- return auth ;
238- }
239- }
240- }
170+ // Attempt to use the docker config file or available credential helpers.
171+ const credentialFromDockerConfig = await getCredentialFromDockerConfigOrCredentialHelper ( params , registry ) ;
172+ if ( credentialFromDockerConfig ) {
173+ return credentialFromDockerConfig ;
174+ }
175+
176+ if ( registry === 'ghcr.io' && githubToken && ( ! githubHost || githubHost === 'ghcr.io' ) ) {
177+ output . write ( '[httpOci] Using environment GITHUB_TOKEN for auth' , LogLevel . Trace ) ;
178+ const userToken = `USERNAME:${ env [ 'GITHUB_TOKEN' ] } ` ;
179+ return {
180+ base64EncodedCredential : Buffer . from ( userToken ) . toString ( 'base64' ) ,
181+ refreshToken : undefined ,
182+ } ;
241183 }
242184
243185 // Represents anonymous access.
@@ -249,17 +191,100 @@ async function existsInPath(filename: string): Promise<boolean> {
249191 if ( ! process . env . PATH ) {
250192 return false ;
251193 }
252- const paths = process . env . PATH . split ( ':' ) ;
253- for ( const path of paths ) {
254- const fullPath = `${ path } /${ filename } ` ;
255- if ( await isLocalFile ( fullPath ) ) {
256- return true ;
194+ try {
195+ const paths = process . env . PATH . split ( ':' ) ;
196+ for ( const path of paths ) {
197+ const fullPath = `${ path } /${ filename } ` ;
198+ if ( await isLocalFile ( fullPath ) ) {
199+ return true ;
200+ }
257201 }
202+ } catch ( err ) {
203+ return false ;
258204 }
259205 return false ;
260206}
261207
262- async function getCredentialFromHelper ( params : CommonParams , registry : string , credHelperName : string ) : Promise < { base64EncodedCredential : string | undefined ; refreshToken : string | undefined } | undefined > {
208+
209+ async function getCredentialFromDockerConfigOrCredentialHelper ( params : CommonParams , registry : string ) {
210+ const { output } = params ;
211+
212+ let configContainsAuth = false ;
213+ try {
214+ const homeDir = os . homedir ( ) ;
215+ if ( homeDir ) {
216+ const dockerConfigPath = path . join ( homeDir , '.docker' , 'config.json' ) ;
217+ if ( await isLocalFile ( dockerConfigPath ) ) {
218+ const dockerConfig : DockerConfigFile = jsonc . parse ( ( await readLocalFile ( dockerConfigPath ) ) . toString ( ) ) ;
219+
220+ configContainsAuth = Object . keys ( dockerConfig . credHelpers || { } ) . length > 0 || ! ! dockerConfig . credsStore || Object . keys ( dockerConfig . auths || { } ) . length > 0 ;
221+ if ( dockerConfig . credHelpers && dockerConfig . credHelpers [ registry ] ) {
222+ const credHelper = dockerConfig . credHelpers [ registry ] ;
223+ output . write ( `[httpOci] Found credential helper '${ credHelper } ' in '${ dockerConfigPath } ' registry '${ registry } '` , LogLevel . Trace ) ;
224+ const auth = await getCredentialFromHelper ( params , registry , credHelper ) ;
225+ if ( auth ) {
226+ return auth ;
227+ }
228+ } else if ( dockerConfig . credsStore ) {
229+ output . write ( `[httpOci] Invoking credsStore credential helper '${ dockerConfig . credsStore } '` , LogLevel . Trace ) ;
230+ const auth = await getCredentialFromHelper ( params , registry , dockerConfig . credsStore ) ;
231+ if ( auth ) {
232+ return auth ;
233+ }
234+ }
235+ if ( dockerConfig . auths && dockerConfig . auths [ registry ] ) {
236+ output . write ( `[httpOci] Found auths entry in '${ dockerConfigPath } ' for registry '${ registry } '` , LogLevel . Trace ) ;
237+ const auth = dockerConfig . auths [ registry ] . auth ;
238+ const identityToken = dockerConfig . auths [ registry ] . identitytoken ; // Refresh token, seen when running: 'az acr login -n <registry>'
239+
240+ if ( identityToken ) {
241+ return {
242+ refreshToken : identityToken ,
243+ base64EncodedCredential : undefined ,
244+ } ;
245+ }
246+
247+ // Without the presence of an `identityToken`, assume auth is a base64-encoded 'user:token'.
248+ return {
249+ base64EncodedCredential : auth ,
250+ refreshToken : undefined ,
251+ } ;
252+ }
253+ }
254+ }
255+ } catch ( err ) {
256+ output . write ( `[httpOci] Failed to read docker config.json: ${ err } ` , LogLevel . Trace ) ;
257+ return ;
258+ }
259+
260+ if ( ! configContainsAuth ) {
261+ let defaultCredHelper = '' ;
262+ // Try platform-specific default credential helper
263+ if ( process . platform === 'linux' ) {
264+ if ( await existsInPath ( 'pass' ) ) {
265+ defaultCredHelper = 'pass' ;
266+ } else {
267+ defaultCredHelper = 'secret' ;
268+ }
269+ } else if ( process . platform === 'win32' ) {
270+ defaultCredHelper = 'wincred' ;
271+ } else if ( process . platform === 'darwin' ) {
272+ defaultCredHelper = 'osxkeychain' ;
273+ }
274+ if ( defaultCredHelper !== '' ) {
275+ output . write ( `[httpOci] Invoking platform default credential helper '${ defaultCredHelper } '` , LogLevel . Trace ) ;
276+ const auth = await getCredentialFromHelper ( params , registry , defaultCredHelper ) ;
277+ if ( auth ) {
278+ return auth ;
279+ }
280+ }
281+ }
282+
283+ // No auth found.
284+ return ;
285+ }
286+
287+ async function getCredentialFromHelper ( params : CommonParams , registry : string , credHelperName : string ) : Promise < { base64EncodedCredential : string | undefined ; refreshToken : string | undefined } | undefined > {
263288 const { output } = params ;
264289
265290 let helperOutput : Buffer ;
0 commit comments