Skip to content

Commit c3f1eee

Browse files
author
Matt Welke
authored
fix: make inline auth work in direct mode too (#520)
## Issue Resolves #517. ## Description The original implementation of inline auth only worked in continuous mode because the code that sets env vars for the AWS SDK was in `Reconcile` and was therefore not invoked by `Validate` (which direct mode invokes). This fixes it by moving that code to `Validate` so that it will run during direct mode too. Also makes the following minor changes: * Renames `accessKeyPair` in the YAML to `credentials` to match other plugins and leave room for other credential related data. * Removes code that reads values for the `AWS_SESSION_TOKEN` env var from secrets and inline config and sets the env var for this. This env var isn't meant to be manually set. Instead, a role is meant to be assumed. Our spec already allows STS to be enabled and a role ARN to be configured when it's enabled. --------- Signed-off-by: Matt Welke <matt.welke@spectrocloud.com>
1 parent abd327f commit c3f1eee

9 files changed

Lines changed: 379 additions & 216 deletions

File tree

api/v1alpha1/awsvalidator_types.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ type AwsAuth struct {
7878
// The secret data's keys and values are expected to align with valid AWS environment variable credentials,
7979
// per the options defined in https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#environment-variables.
8080
SecretName string `json:"secretName,omitempty" yaml:"secretName,omitempty"`
81-
// The credentials to use when running in direct mode. If provided, fields Implicit and SecretName are ignored.
82-
Credentials *Credentials `json:"accessKeyPair,omitempty" yaml:"accessKeyPair,omitempty"`
81+
// The credentials to use when running in direct mode.
82+
Credentials *Credentials `json:"credentials,omitempty" yaml:"credentials,omitempty"`
8383
// STS authentication properties (optional)
8484
StsAuth *AwsSTSAuth `json:"stsAuth,omitempty" yaml:"stsAuth,omitempty"`
8585
}
@@ -90,8 +90,6 @@ type Credentials struct {
9090
AccessKeyID string `json:"accessKeyId" yaml:"accessKeyId"`
9191
// The secret access key of an access key pair.
9292
SecretAccessKey string `json:"secretAccessKey" yaml:"secretAccessKey"`
93-
// The session token if one is being used.
94-
SessionToken *string `json:"sessionToken" yaml:"sessionToken"`
9593
}
9694

9795
// AwsSTSAuth defines AWS STS authentication configuration for an AwsValidator.

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 1 addition & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

chart/validator-plugin-aws/crds/validation.spectrocloud.labs_awsvalidators.yaml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,23 +89,18 @@ spec:
8989
auth:
9090
description: AwsAuth defines authentication configuration for an AwsValidator.
9191
properties:
92-
accessKeyPair:
92+
credentials:
9393
description: The credentials to use when running in direct mode.
94-
If provided, fields Implicit and SecretName are ignored.
9594
properties:
9695
accessKeyId:
9796
description: The access key ID of an access key pair.
9897
type: string
9998
secretAccessKey:
10099
description: The secret access key of an access key pair.
101100
type: string
102-
sessionToken:
103-
description: The session token if one is being used.
104-
type: string
105101
required:
106102
- accessKeyId
107103
- secretAccessKey
108-
- sessionToken
109104
type: object
110105
implicit:
111106
description: |-

config/crd/bases/validation.spectrocloud.labs_awsvalidators.yaml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,23 +89,18 @@ spec:
8989
auth:
9090
description: AwsAuth defines authentication configuration for an AwsValidator.
9191
properties:
92-
accessKeyPair:
92+
credentials:
9393
description: The credentials to use when running in direct mode.
94-
If provided, fields Implicit and SecretName are ignored.
9594
properties:
9695
accessKeyId:
9796
description: The access key ID of an access key pair.
9897
type: string
9998
secretAccessKey:
10099
description: The secret access key of an access key pair.
101100
type: string
102-
sessionToken:
103-
description: The session token if one is being used.
104-
type: string
105101
required:
106102
- accessKeyId
107103
- secretAccessKey
108-
- sessionToken
109104
type: object
110105
implicit:
111106
description: |-

go.mod

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@ require (
1717
github.com/go-logr/logr v1.4.2
1818
github.com/onsi/ginkgo/v2 v2.22.0
1919
github.com/onsi/gomega v1.35.1
20-
github.com/pkg/errors v0.9.1
20+
github.com/stretchr/testify v1.9.0
2121
github.com/validator-labs/validator v0.1.13
2222
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
2323
k8s.io/api v0.31.3
2424
k8s.io/apimachinery v0.31.3
2525
k8s.io/client-go v0.31.3
26-
k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078
2726
sigs.k8s.io/cluster-api v1.8.5
2827
sigs.k8s.io/controller-runtime v0.19.2
2928
)
@@ -71,6 +70,8 @@ require (
7170
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
7271
github.com/modern-go/reflect2 v1.0.2 // indirect
7372
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
73+
github.com/pkg/errors v0.9.1 // indirect
74+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
7475
github.com/prometheus/client_golang v1.19.1 // indirect
7576
github.com/prometheus/client_model v0.6.1 // indirect
7677
github.com/prometheus/common v0.55.0 // indirect
@@ -89,12 +90,14 @@ require (
8990
golang.org/x/tools v0.27.0 // indirect
9091
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
9192
google.golang.org/protobuf v1.35.2 // indirect
93+
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
9294
gopkg.in/inf.v0 v0.9.1 // indirect
9395
gopkg.in/yaml.v2 v2.4.0 // indirect
9496
gopkg.in/yaml.v3 v3.0.1 // indirect
9597
k8s.io/apiextensions-apiserver v0.31.0 // indirect
9698
k8s.io/klog/v2 v2.130.1 // indirect
9799
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
100+
k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 // indirect
98101
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
99102
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
100103
sigs.k8s.io/yaml v1.4.0 // indirect

internal/controller/awsvalidator_controller.go

Lines changed: 35 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,13 @@ package controller
2020
import (
2121
"context"
2222
"fmt"
23-
"os"
2423
"time"
2524

2625
"github.com/go-logr/logr"
27-
"github.com/pkg/errors"
2826
corev1 "k8s.io/api/core/v1"
2927
apierrs "k8s.io/apimachinery/pkg/api/errors"
3028
"k8s.io/apimachinery/pkg/runtime"
3129
ktypes "k8s.io/apimachinery/pkg/types"
32-
"k8s.io/utils/ptr"
3330
"sigs.k8s.io/cluster-api/util/patch"
3431
ctrl "sigs.k8s.io/controller-runtime"
3532
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -40,9 +37,10 @@ import (
4037
vres "github.com/validator-labs/validator/pkg/validationresult"
4138
)
4239

43-
var errInvalidAccessKeyID = errors.New("access key ID is invalid, must be a non-empty string")
44-
var errInvalidSecretAccessKey = errors.New("secret access key is invalid, must be a non-empty string")
45-
var errInvalidSessionToken = errors.New("session token is invalid, must be a non-empty string")
40+
const (
41+
secretKeyAccessKeyID = "AWS_ACCESS_KEY_ID" // #nosec G101
42+
secretKeySecretAccessKey = "AWS_SECRET_ACCESS_KEY" // #nosec G101
43+
)
4644

4745
// AwsValidatorReconciler reconciles a AwsValidator object
4846
type AwsValidatorReconciler struct {
@@ -57,6 +55,7 @@ type AwsValidatorReconciler struct {
5755

5856
// Reconcile reconciles each rule found in each AWSValidator in the cluster and creates ValidationResults accordingly
5957
func (r *AwsValidatorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
58+
var err error
6059
l := r.Log.V(0).WithValues("name", req.Name, "namespace", req.Namespace)
6160
l.Info("Reconciling AwsValidator")
6261

@@ -68,9 +67,9 @@ func (r *AwsValidatorReconciler) Reconcile(ctx context.Context, req ctrl.Request
6867
return ctrl.Result{}, client.IgnoreNotFound(err)
6968
}
7069

71-
// Ensure both AWS env vars are set.
72-
if err := r.configureAwsAuth(validator.Spec.Auth, req.Namespace, l); err != nil {
73-
return ctrl.Result{}, fmt.Errorf("failed to set AWS auth env vars: %w", err)
70+
// Override auth data in spec with auth data from Secret if applicable.
71+
if validator.Spec.Auth, err = r.authFromSecret(validator.Spec.Auth, req.Namespace, l); err != nil {
72+
return ctrl.Result{}, fmt.Errorf("failed to get auth data from Secret: %w", err)
7473
}
7574

7675
// Get the active validator's validation result
@@ -111,78 +110,46 @@ func (r *AwsValidatorReconciler) Reconcile(ctx context.Context, req ctrl.Request
111110
return ctrl.Result{RequeueAfter: time.Second * 120}, nil
112111
}
113112

114-
// configureAwsAuth sets environment variables to control AWS authentication. Order of precedence
115-
// for source:
116-
// 1 - Kubernetes secret
117-
// 2 - Specified inline in spec
118-
// Returns an error if env vars couldn't be set for any reason.
119-
func (r *AwsValidatorReconciler) configureAwsAuth(auth v1alpha1.AwsAuth, reqNamespace string, l logr.Logger) error {
113+
// Checks whether the spec indicates that auth data should come from a k8s Secret instead of inline
114+
// auth. If so, all data must come from the Secret. If any is missing, returns an error. If all data
115+
// is present, overrides the data in the auth object.
116+
func (r *AwsValidatorReconciler) authFromSecret(auth v1alpha1.AwsAuth, reqNamespace string, l logr.Logger) (v1alpha1.AwsAuth, error) {
117+
// If using implicit auth, there is no need to check for k8s Secrets.
120118
if auth.Implicit {
121-
l.Info("auth.implicit set to true. Skipping setting AWS env vars.")
122-
return nil
119+
l.Info("auth.implicit set to true. Skipping setting AWS_ env vars.")
120+
return auth, nil
123121
}
124122

125-
if auth.Credentials == nil {
126-
auth.Credentials = &v1alpha1.Credentials{}
123+
// Same if no secret name provided.
124+
if auth.SecretName == "" {
125+
l.Info("No Secret name provided. Skipping looking for Secret to override auth data.")
126+
return auth, nil
127127
}
128128

129-
// If Secret name provided, override any env var values with values from its data.
130-
if auth.SecretName != "" {
131-
l.Info("auth.secretName provided. Using Secret as source for any AWS env vars defined in its data.", "secretName", auth.SecretName, "secretNamespace", reqNamespace)
132-
nn := ktypes.NamespacedName{Name: auth.SecretName, Namespace: reqNamespace}
133-
secret := &corev1.Secret{}
134-
if err := r.Get(context.Background(), nn, secret); err != nil {
135-
return fmt.Errorf("failed to get Secret: %w", err)
136-
}
137-
if accessKeyID, ok := secret.Data["AWS_ACCESS_KEY_ID"]; ok {
138-
l.Info("Using access key ID from Secret.")
139-
auth.Credentials.AccessKeyID = string(accessKeyID)
140-
}
141-
if secretAccessKey, ok := secret.Data["AWS_SECRET_ACCESS_KEY"]; ok {
142-
l.Info("Using secret access key from Secret.")
143-
auth.Credentials.SecretAccessKey = string(secretAccessKey)
144-
}
145-
if sessionToken, ok := secret.Data["AWS_SESSION_TOKEN"]; ok {
146-
l.Info("Using session token from Secret.")
147-
auth.Credentials.SessionToken = ptr.To(string(sessionToken))
148-
}
129+
if auth.Credentials == nil {
130+
auth.Credentials = &v1alpha1.Credentials{}
149131
}
150132

151-
// Validate values collected from inline config and/or Secret. We can't rely on CRD validation
152-
// for this because some of the values may have come from a Secret, and there is no way for the
153-
// Kube API to validate content in its data.
154-
if auth.Credentials.AccessKeyID == "" {
155-
return errInvalidAccessKeyID
156-
}
157-
if auth.Credentials.SecretAccessKey == "" {
158-
return errInvalidSecretAccessKey
159-
}
160-
if auth.Credentials.SessionToken != nil && *auth.Credentials.SessionToken == "" {
161-
return errInvalidSessionToken
133+
l.Info("auth.secretName provided. Using Secret as source for any AWS_ env vars defined in its data.", "secretName", auth.SecretName, "secretNamespace", reqNamespace)
134+
nn := ktypes.NamespacedName{Name: auth.SecretName, Namespace: reqNamespace}
135+
secret := &corev1.Secret{}
136+
if err := r.Get(context.Background(), nn, secret); err != nil {
137+
return auth, fmt.Errorf("failed to get Secret: %w", err)
162138
}
163139

164-
// Log non-secret data for help with debugging. Don't log the secret access key.
165-
nonSecretData := map[string]string{
166-
"accesskeyId": auth.Credentials.AccessKeyID,
140+
accessKeyID, ok := secret.Data[secretKeyAccessKeyID]
141+
if !ok {
142+
return v1alpha1.AwsAuth{}, fmt.Errorf("Key %s missing from Secret", secretKeyAccessKeyID)
167143
}
168-
l.Info("Determined AWS auth data.", "nonSecretData", nonSecretData)
144+
auth.Credentials.AccessKeyID = string(accessKeyID)
169145

170-
// Use collected and validated values to set env vars.
171-
data := map[string]string{
172-
"AWS_ACCESS_KEY_ID": auth.Credentials.AccessKeyID,
173-
"AWS_SECRET_ACCESS_KEY": auth.Credentials.SecretAccessKey,
174-
}
175-
if auth.Credentials.SessionToken != nil {
176-
data["AWS_SESSION_TOKEN"] = *auth.Credentials.SessionToken
177-
}
178-
for k, v := range data {
179-
if err := os.Setenv(k, v); err != nil {
180-
return err
181-
}
182-
r.Log.Info("Set environment variable", "envVar", k)
146+
secretAccessKey, ok := secret.Data[secretKeySecretAccessKey]
147+
if !ok {
148+
return v1alpha1.AwsAuth{}, fmt.Errorf("Key %s missing from Secret", secretKeySecretAccessKey)
183149
}
150+
auth.Credentials.SecretAccessKey = string(secretAccessKey)
184151

185-
return nil
152+
return auth, nil
186153
}
187154

188155
// SetupWithManager sets up the controller with the Manager.

0 commit comments

Comments
 (0)