Skip to content

Commit bc2bd99

Browse files
author
Moritz Clasmeier
committed
Implementation of pull secret injection
1 parent 19aff82 commit bc2bd99

1 file changed

Lines changed: 125 additions & 0 deletions

File tree

internal/deployer/openshift.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package deployer
2+
3+
import (
4+
"context"
5+
"encoding/base64"
6+
"encoding/json"
7+
"errors"
8+
"fmt"
9+
10+
"github.com/stackrox/roxie/internal/dockerauth"
11+
v1 "k8s.io/api/core/v1"
12+
k8sapierrors "k8s.io/apimachinery/pkg/api/errors"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
"k8s.io/client-go/util/retry"
15+
)
16+
17+
const (
18+
openshiftConfigNamespace = "openshift-config"
19+
openshiftGlobalPullSecretName = "pull-secret"
20+
dockerConfigJsonKey = ".dockerconfigjson"
21+
registryForDownstreamImages = "quay.io/rhacs-eng"
22+
)
23+
24+
var (
25+
namespacedGlobalPullSecret = openshiftConfigNamespace + "/" + openshiftGlobalPullSecretName
26+
)
27+
28+
// dockerConfigJSON represents the structure of a .dockerconfigjson secret value.
29+
type dockerConfigJSON struct {
30+
Auths map[string]dockerauth.AuthEntry `json:"auths"`
31+
}
32+
33+
// InjectGlobalOpenShiftPullSecret adds registry credentials to the OpenShift global pull secret.
34+
func (d *Deployer) InjectGlobalOpenShiftPullSecret(ctx context.Context) error {
35+
// Retry on Conflict, AlreadyExists, and NotFound to handle TOCTOU races between
36+
// the Get and subsequent Create/Update (e.g., secret deleted after Get -> Update
37+
// returns NotFound; secret created by another caller after Get -> Create returns
38+
// AlreadyExists).
39+
return retry.OnError(retry.DefaultRetry, func(err error) bool {
40+
return k8sapierrors.IsConflict(err) || k8sapierrors.IsAlreadyExists(err) || k8sapierrors.IsNotFound(err)
41+
}, func() error {
42+
return d.injectGlobalOpenShiftPullSecretOnce(ctx)
43+
})
44+
}
45+
46+
func (d *Deployer) injectGlobalOpenShiftPullSecretOnce(ctx context.Context) error {
47+
if d.dockerCreds == nil {
48+
return errors.New("no pull secrets found")
49+
}
50+
credentials := *d.dockerCreds
51+
52+
if d.k8sClient == nil {
53+
return errors.New("k8s client not initialized")
54+
}
55+
56+
secrets := d.k8sClient.CoreV1().Secrets(openshiftConfigNamespace)
57+
secret, err := secrets.Get(ctx, openshiftGlobalPullSecretName, metav1.GetOptions{})
58+
if err != nil {
59+
if !k8sapierrors.IsNotFound(err) {
60+
return fmt.Errorf("retrieving secret %s: %w", namespacedGlobalPullSecret, err)
61+
}
62+
secret = &v1.Secret{
63+
ObjectMeta: metav1.ObjectMeta{
64+
Name: openshiftGlobalPullSecretName,
65+
Namespace: openshiftConfigNamespace,
66+
},
67+
Type: v1.SecretTypeDockerConfigJson,
68+
}
69+
}
70+
71+
modified, err := injectRegistryCredentialsIntoSecret(credentials, secret)
72+
if err != nil {
73+
return fmt.Errorf("injecting registry credentials into Kubernetes secret: %w", err)
74+
}
75+
if !modified {
76+
d.logger.Dimf("Global pull secret %s already contains entry for %s, skipping", namespacedGlobalPullSecret, registryForDownstreamImages)
77+
return nil
78+
}
79+
80+
if secret.ResourceVersion == "" {
81+
if _, err := secrets.Create(ctx, secret, metav1.CreateOptions{}); err != nil {
82+
return fmt.Errorf("creating secret %s: %w", namespacedGlobalPullSecret, err)
83+
}
84+
} else {
85+
if _, err := secrets.Update(ctx, secret, metav1.UpdateOptions{}); err != nil {
86+
return fmt.Errorf("updating secret %s: %w", namespacedGlobalPullSecret, err)
87+
}
88+
}
89+
90+
d.logger.Successf("Injected pull secret for %s into %s", registryForDownstreamImages, namespacedGlobalPullSecret)
91+
return nil
92+
}
93+
94+
// injectRegistryCredentialsIntoSecret mutates the secret in place, returning true if it was modified.
95+
func injectRegistryCredentialsIntoSecret(credentials dockerauth.Credentials, secret *v1.Secret) (bool, error) {
96+
var cfg dockerConfigJSON
97+
if data, ok := secret.Data[dockerConfigJsonKey]; ok {
98+
if err := json.Unmarshal(data, &cfg); err != nil {
99+
return false, fmt.Errorf("unmarshaling %q in %s: %w", dockerConfigJsonKey, namespacedGlobalPullSecret, err)
100+
}
101+
}
102+
if cfg.Auths == nil {
103+
cfg.Auths = make(map[string]dockerauth.AuthEntry)
104+
}
105+
106+
if _, ok := cfg.Auths[registryForDownstreamImages]; ok {
107+
return false, nil
108+
}
109+
110+
cfg.Auths[registryForDownstreamImages] = dockerauth.AuthEntry{
111+
Auth: base64.StdEncoding.EncodeToString([]byte(credentials.Username + ":" + credentials.Password)),
112+
}
113+
114+
updated, err := json.Marshal(cfg)
115+
if err != nil {
116+
return false, fmt.Errorf("marshaling updated docker config: %w", err)
117+
}
118+
119+
if secret.Data == nil {
120+
secret.Data = make(map[string][]byte)
121+
}
122+
secret.Data[dockerConfigJsonKey] = updated
123+
124+
return true, nil
125+
}

0 commit comments

Comments
 (0)