@@ -1028,13 +1028,19 @@ services:
10281028
10291029 // Start the token server inside the testing container.
10301030 // The binary is built on the host and mounted via the auth volume.
1031+ // Use short-lived tokens (3s). After expiry, the cached Bearer handler
1032+ // in docker.Authorizer tries to re-fetch a token from the token server
1033+ // using the baked-in credentials. If the password has changed, fetchToken
1034+ // fails inside doBearerAuth — AddResponses is never called, so the handler
1035+ // is never deleted. The Authorizer is stuck with stale credentials.
10311036 sh .Gox ("/auth/tokenserver" ,
10321037 "--addr" , ":5001" ,
10331038 "--cert" , "/auth/domain.crt" ,
10341039 "--key" , "/auth/domain.key" ,
10351040 "--signing-key" , "/auth/signing.key" ,
10361041 "--password-file" , "/auth/password.txt" ,
1037- "--issuer" , "test-issuer" )
1042+ "--issuer" , "test-issuer" ,
1043+ "--token-ttl" , "3" )
10381044 // Wait for token server to start.
10391045 sh .Retry (30 , "curl" , "-sk" , "https://localhost:5001/token" )
10401046
@@ -1056,9 +1062,12 @@ services:
10561062 sh .X (append (imagePullCmd , "--soci-index-digest" , indexDigest , image )... )
10571063
10581064 // First run: warms up registryHostMap cache. The docker.Authorizer creates
1059- // a Bearer handler that caches the token server credentials internally .
1065+ // a Bearer handler that caches the token and token server credentials.
10601066 sh .X (append (runSociCmd , "--name" , "warmup" , "--rm" , image , "echo" , "ok" )... )
10611067
1068+ // Wait for the short-lived token to expire (3s TTL + buffer).
1069+ sh .X ("sleep" , "5" )
1070+
10621071 // Rotate the token server password.
10631072 newPass := "rotatedpass"
10641073 if err := os .WriteFile (filepath .Join (authDir , "password.txt" ), []byte (newPass ), 0666 ); err != nil {
@@ -1068,15 +1077,18 @@ services:
10681077 // Login with the new password (updates docker config keychain).
10691078 sh .X ("nerdctl" , "login" , "-u" , regConfig .user , "-p" , newPass , regConfig .host )
10701079
1071- // Second run: check() fires. The cached AuthClient's Bearer handler tries
1072- // to fetch a token from the token server using the OLD password (baked into
1073- // the handler at creation time). The token server rejects it. This error
1074- // occurs inside doBearerAuth → fetchToken, so AddResponses is never called
1075- // and the handler is never deleted.
1080+ // Second run: check() fires (check_always=true). The cached AuthClient's
1081+ // docker.Authorizer has a Bearer handler whose token has expired.
1082+ // doBearerAuth calls fetchToken with the OLD password baked into the
1083+ // handler's TokenOptions. The token server rejects it. This error occurs
1084+ // inside doBearerAuth — AddResponses is never called, so the handler is
1085+ // never deleted. The Authorizer is stuck with stale credentials.
10761086 //
10771087 // Without InvalidateRegistryHosts: stuck with stale handler → permanent failure.
1078- // With InvalidateRegistryHosts: cache cleared → fresh Authorizer → new handler
1079- // with new password from docker config → token server accepts → success.
1088+ // With InvalidateRegistryHosts: cache cleared → fresh Authorizer → empty
1089+ // handlers map → first request goes unauthenticated → registry returns 401
1090+ // Bearer challenge → AddResponses creates new handler with fresh password
1091+ // from docker config → fetchToken succeeds → container runs.
10801092 _ , err = sh .OLog (append (runSociCmd , "--name" , "after-rotation" , "--rm" , image , "echo" , "ok" )... )
10811093 if err != nil {
10821094 t .Fatalf ("expected container run to succeed after credential rotation, got: %v" , err )
0 commit comments