@@ -19,7 +19,8 @@ const (
1919
2020// DockerAuth handles Docker authentication and pull secret management.
2121type DockerAuth struct {
22- logger * logger.Logger
22+ logger * logger.Logger
23+ skipCredVerification bool
2324}
2425
2526// DockerConfig represents Docker configuration structure.
@@ -40,26 +41,33 @@ type CredentialData struct {
4041 Secret string `json:"Secret"`
4142}
4243
44+ // Credentials represents verified Docker credentials.
45+ type Credentials struct {
46+ Username string
47+ Password string
48+ }
49+
4350// New creates a new DockerAuth instance.
4451func New (log * logger.Logger ) * DockerAuth {
4552 return & DockerAuth {
4653 logger : log ,
4754 }
4855}
4956
50- // GetDockerAuthString generates Docker authentication string for image pull secrets
51- func (d * DockerAuth ) GetDockerAuthString (_ , _ string ) (string , error ) {
57+ // GetAndVerifyCredentials retrieves and verifies Docker credentials.
58+ // This should be called early to fail fast if credentials are invalid.
59+ func (d * DockerAuth ) GetAndVerifyCredentials () (* Credentials , error ) {
5260 var username , password string
5361
5462 // Try environment variables first.
5563 username = os .Getenv ("REGISTRY_USERNAME" )
5664 password = os .Getenv ("REGISTRY_PASSWORD" )
5765
5866 if username != "" && password == "" {
59- return "" , errors .New ("REGISTRY_USERNAME set but REGISTRY_PASSWORD is empty" )
67+ return nil , errors .New ("REGISTRY_USERNAME set but REGISTRY_PASSWORD is empty" )
6068 }
6169 if username == "" && password != "" {
62- return "" , errors .New ("REGISTRY_PASSWORD set but REGISTRY_USERNAME is empty" )
70+ return nil , errors .New ("REGISTRY_PASSWORD set but REGISTRY_USERNAME is empty" )
6371 }
6472
6573 if username == "" {
@@ -70,31 +78,26 @@ func (d *DockerAuth) GetDockerAuthString(_, _ string) (string, error) {
7078 var err error
7179 username , password , err = d .getCredentialsFromDockerConfig (dockerConfigPath )
7280 if err != nil {
73- return "" , err
81+ return nil , err
7482 }
7583 }
7684 }
7785
7886 if username == "" || password == "" {
79- return "" , errors .New ("no Docker credentials found" )
80- }
81-
82- // Create auth string.
83- authString := fmt .Sprintf ("%s:%s" , username , password )
84- encodedAuth := base64 .StdEncoding .EncodeToString ([]byte (authString ))
85-
86- dockerConfig := DockerConfig {
87- Auths : map [string ]AuthEntry {
88- acsImageRegistry : {Auth : encodedAuth },
89- },
87+ return nil , errors .New ("no Docker credentials found" )
9088 }
9189
92- jsonData , err := json .Marshal (dockerConfig )
93- if err != nil {
94- return "" , fmt .Errorf ("failed to marshal Docker config: %w" , err )
90+ // Verify credentials.
91+ if ! d .skipCredVerification {
92+ if err := d .VerifyCredentials (username , password ); err != nil {
93+ return nil , fmt .Errorf ("credentials are invalid: %w" , err )
94+ }
9595 }
9696
97- return string (jsonData ), nil
97+ return & Credentials {
98+ Username : username ,
99+ Password : password ,
100+ }, nil
98101}
99102
100103// getCredentialsFromDockerConfig extracts credentials from existing Docker config.
@@ -172,14 +175,60 @@ func (d *DockerAuth) getCredentialFromHelper(helperName, registry string) (*Cred
172175 return & credData , nil
173176}
174177
175- // CreatePullSecretYAML creates Kubernetes pull secret YAML.
176- func (d * DockerAuth ) CreatePullSecretYAML (namespace string ) (string , error ) {
177- dockerConfigJSON , err := d .GetDockerAuthString ("" , "" )
178+ // VerifyCredentials attempts to verify that the credentials work by making a request to the registry.
179+ // This uses a read-only HTTP request.
180+ // It mimics what the kubelet would do when pulling images.
181+ func (d * DockerAuth ) VerifyCredentials (username , password string ) error {
182+ // Create auth header for Basic authentication
183+ authString := fmt .Sprintf ("%s:%s" , username , password )
184+ encodedAuth := base64 .StdEncoding .EncodeToString ([]byte (authString ))
185+
186+ // Try to get a token from quay.io's OAuth2 endpoint for a specific repository
187+ // This mimics what kubelet does when pulling images - it requests a token with pull scope
188+ // for the specific repository.
189+ repository := "rhacs-eng/main"
190+ authURL := fmt .Sprintf ("https://%s/v2/auth?service=%s&scope=repository:%s:pull" ,
191+ acsImageRegistry , acsImageRegistry , repository )
192+
193+ cmd := exec .Command ("curl" , "-s" , "-f" ,
194+ "-H" , fmt .Sprintf ("Authorization: Basic %s" , encodedAuth ),
195+ authURL )
196+
197+ output , err := cmd .CombinedOutput ()
178198 if err != nil {
179- return "" , err
199+ d .logger .Warningf ("Failed to verify credentials for %s: %v" , acsImageRegistry , err )
200+ d .logger .Dimf ("Verification output: %s" , string (output ))
201+ return fmt .Errorf ("credential verification failed for %s: %w" , acsImageRegistry , err )
202+ }
203+
204+ // Check if we got a valid JSON response with a token
205+ var tokenResponse map [string ]interface {}
206+ if err := json .Unmarshal (output , & tokenResponse ); err != nil {
207+ return fmt .Errorf ("credential verification failed: invalid response from %s: %w" , acsImageRegistry , err )
208+ }
209+
210+ if _ , ok := tokenResponse ["token" ]; ! ok {
211+ return fmt .Errorf ("credential verification failed: no token received from %s" , acsImageRegistry )
212+ }
213+
214+ d .logger .Dimf ("Successfully verified credentials for %s (repository: %s)" , acsImageRegistry , repository )
215+ return nil
216+ }
217+
218+ // CreatePullSecretYAMLFromCredentials creates Kubernetes pull secret YAML from verified credentials.
219+ func (d * DockerAuth ) CreatePullSecretYAMLFromCredentials (creds * Credentials , namespace string ) string {
220+ // Create auth string
221+ authString := fmt .Sprintf ("%s:%s" , creds .Username , creds .Password )
222+ encodedAuth := base64 .StdEncoding .EncodeToString ([]byte (authString ))
223+
224+ dockerConfig := DockerConfig {
225+ Auths : map [string ]AuthEntry {
226+ acsImageRegistry : {Auth : encodedAuth },
227+ },
180228 }
181229
182- encodedConfig := base64 .StdEncoding .EncodeToString ([]byte (dockerConfigJSON ))
230+ jsonData , _ := json .Marshal (dockerConfig )
231+ encodedConfig := base64 .StdEncoding .EncodeToString (jsonData )
183232
184233 secretYAML := fmt .Sprintf (`apiVersion: v1
185234kind: Secret
@@ -191,5 +240,5 @@ data:
191240 .dockerconfigjson: %s
192241` , namespace , encodedConfig )
193242
194- return secretYAML , nil
243+ return secretYAML
195244}
0 commit comments