Skip to content

Commit b686871

Browse files
authored
Merge pull request #171 from kube-logging/refactor/output-secret-population
refactor: make output secret validatable
2 parents e16b265 + a8f2771 commit b686871

File tree

7 files changed

+98
-44
lines changed

7 files changed

+98
-44
lines changed

api/telemetry/v1alpha1/output_types.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,12 @@ type OutputSpec struct {
6969
Batch *Batch `json:"batch,omitempty"`
7070
}
7171

72+
// +kubebuilder:validation:XValidation:rule="(has(self.basicAuth) && has(self.bearerAuth)) == false",message="Only one authentication method can be specified: either basicAuth or bearerAuth, not both"
73+
7274
// Output Authentication configuration.
7375
type OutputAuth struct {
74-
BasicAuth *BasicAuthConfig `json:"basicauth,omitempty"`
75-
BearerAuth *BearerAuthConfig `json:"bearerauth,omitempty"`
76+
BasicAuth *BasicAuthConfig `json:"basicAuth,omitempty"`
77+
BearerAuth *BearerAuthConfig `json:"bearerAuth,omitempty"`
7678
}
7779

7880
type BasicAuthConfig struct {

charts/telemetry-controller/crds/telemetry.kube-logging.dev_outputs.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ spec:
5151
authentication:
5252
description: Output Authentication configuration.
5353
properties:
54-
basicauth:
54+
basicAuth:
5555
properties:
5656
passwordField:
5757
type: string
@@ -73,7 +73,7 @@ spec:
7373
usernameField:
7474
type: string
7575
type: object
76-
bearerauth:
76+
bearerAuth:
7777
properties:
7878
secretRef:
7979
description: |-
@@ -94,6 +94,10 @@ spec:
9494
type: string
9595
type: object
9696
type: object
97+
x-kubernetes-validations:
98+
- message: 'Only one authentication method can be specified: either
99+
basicAuth or bearerAuth, not both'
100+
rule: (has(self.basicAuth) && has(self.bearerAuth)) == false
97101
batch:
98102
description: Batch processor configuration.
99103
properties:

config/crd/bases/telemetry.kube-logging.dev_outputs.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ spec:
5151
authentication:
5252
description: Output Authentication configuration.
5353
properties:
54-
basicauth:
54+
basicAuth:
5555
properties:
5656
passwordField:
5757
type: string
@@ -73,7 +73,7 @@ spec:
7373
usernameField:
7474
type: string
7575
type: object
76-
bearerauth:
76+
bearerAuth:
7777
properties:
7878
secretRef:
7979
description: |-
@@ -94,6 +94,10 @@ spec:
9494
type: string
9595
type: object
9696
type: object
97+
x-kubernetes-validations:
98+
- message: 'Only one authentication method can be specified: either
99+
basicAuth or bearerAuth, not both'
100+
rule: (has(self.basicAuth) && has(self.bearerAuth)) == false
97101
batch:
98102
description: Batch processor configuration.
99103
properties:

controllers/telemetry/collector_controller.go

Lines changed: 11 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,6 @@ type CollectorReconciler struct {
6161
Scheme *runtime.Scheme
6262
}
6363

64-
type BasicAuthClientAuthConfig struct {
65-
Username string
66-
Password string
67-
}
68-
6964
func (r *CollectorReconciler) buildConfigInputForCollector(ctx context.Context, collector *v1alpha1.Collector) (otelcolconfgen.OtelColConfigInput, error) {
7065
logger := log.FromContext(ctx)
7166
tenantSubscriptionMap := make(map[string][]v1alpha1.NamespacedName)
@@ -114,18 +109,23 @@ func (r *CollectorReconciler) buildConfigInputForCollector(ctx context.Context,
114109
outputNames := subscription.Status.Outputs
115110
subscriptionOutputMap[subscription.NamespacedName()] = outputNames
116111
for _, outputName := range outputNames {
117-
outputWithSecretData := components.OutputWithSecretData{}
118-
119112
queriedOutput := &v1alpha1.Output{}
120113
if err = r.Get(ctx, types.NamespacedName(outputName), queriedOutput); err != nil {
121114
logger.Error(errors.WithStack(err), "failed getting outputs for subscription", "subscription", subscription.NamespacedName().String())
122115
return otelcolconfgen.OtelColConfigInput{}, err
123116
}
124-
outputWithSecretData.Output = *queriedOutput
125-
126-
if err := r.populateSecretForOutput(ctx, queriedOutput, &outputWithSecretData); err != nil {
127-
return otelcolconfgen.OtelColConfigInput{}, err
117+
outputWithSecretData := components.OutputWithSecretData{
118+
Output: *queriedOutput,
128119
}
120+
if queriedOutput.Spec.Authentication != nil {
121+
outputSecret, err := components.QueryOutputSecretWithData(ctx, r.Client, queriedOutput)
122+
if err != nil {
123+
logger.Error(errors.WithStack(err), "failed querying output secret for output", "output", queriedOutput.NamespacedName().String())
124+
} else {
125+
outputWithSecretData.Secret = *outputSecret
126+
}
127+
}
128+
129129
outputs = append(outputs, outputWithSecretData)
130130
}
131131
}
@@ -144,31 +144,6 @@ func (r *CollectorReconciler) buildConfigInputForCollector(ctx context.Context,
144144
}, nil
145145
}
146146

147-
func (r *CollectorReconciler) populateSecretForOutput(ctx context.Context, queriedOutput *v1alpha1.Output, outputWithSecret *components.OutputWithSecretData) error {
148-
logger := log.FromContext(ctx)
149-
150-
if queriedOutput.Spec.Authentication != nil {
151-
if queriedOutput.Spec.Authentication.BasicAuth != nil && queriedOutput.Spec.Authentication.BasicAuth.SecretRef != nil {
152-
queriedSecret := &corev1.Secret{}
153-
if err := r.Get(ctx, types.NamespacedName{Namespace: queriedOutput.Spec.Authentication.BasicAuth.SecretRef.Namespace, Name: queriedOutput.Spec.Authentication.BasicAuth.SecretRef.Name}, queriedSecret); err != nil {
154-
logger.Error(errors.WithStack(err), "failed getting secrets for output", "output", queriedOutput.NamespacedName().String())
155-
return err
156-
}
157-
outputWithSecret.Secret = *queriedSecret
158-
}
159-
if queriedOutput.Spec.Authentication.BearerAuth != nil && queriedOutput.Spec.Authentication.BearerAuth.SecretRef != nil {
160-
queriedSecret := &corev1.Secret{}
161-
if err := r.Get(ctx, types.NamespacedName{Namespace: queriedOutput.Spec.Authentication.BearerAuth.SecretRef.Namespace, Name: queriedOutput.Spec.Authentication.BearerAuth.SecretRef.Name}, queriedSecret); err != nil {
162-
logger.Error(errors.WithStack(err), "failed getting secrets for output", "output", queriedOutput.NamespacedName().String())
163-
return err
164-
}
165-
outputWithSecret.Secret = *queriedSecret
166-
}
167-
}
168-
169-
return nil
170-
}
171-
172147
// +kubebuilder:rbac:groups=telemetry.kube-logging.dev,resources=collectors;tenants;subscriptions;outputs;bridges;,verbs=get;list;watch;create;update;patch;delete
173148
// +kubebuilder:rbac:groups=telemetry.kube-logging.dev,resources=collectors/status;tenants/status;subscriptions/status;outputs/status;bridges/status;,verbs=get;update;patch
174149
// +kubebuilder:rbac:groups=telemetry.kube-logging.dev,resources=collectors/finalizers,verbs=update

controllers/telemetry/route_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ type RouteReconciler struct {
5050
// +kubebuilder:rbac:groups=telemetry.kube-logging.dev,resources=collectors;tenants;subscriptions;outputs;bridges;,verbs=get;list;watch;create;update;patch;delete
5151
// +kubebuilder:rbac:groups=telemetry.kube-logging.dev,resources=collectors/status;tenants/status;subscriptions/status;outputs/status;bridges/status;,verbs=get;update;patch
5252
// +kubebuilder:rbac:groups=telemetry.kube-logging.dev,resources=collectors/finalizers,verbs=update
53-
// +kubebuilder:rbac:groups="",resources=nodes;namespaces;endpoints;nodes/proxy,verbs=get;list;watch
53+
// +kubebuilder:rbac:groups="",resources=secrets;nodes;namespaces;endpoints;nodes/proxy,verbs=get;list;watch
5454
// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=clusterroles;clusterrolebindings;roles;rolebindings,verbs=get;list;watch;create;update;patch;delete
5555
// +kubebuilder:rbac:groups="",resources=services;persistentvolumeclaims;serviceaccounts;pods,verbs=get;list;watch;create;update;patch;delete
5656
// +kubebuilder:rbac:groups=apps,resources=statefulsets;daemonsets;replicasets,verbs=get;list;watch;create;update;patch;delete

pkg/resources/manager/tenant_resource_manager.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"sigs.k8s.io/controller-runtime/pkg/client"
3131

3232
"github.com/kube-logging/telemetry-controller/api/telemetry/v1alpha1"
33+
"github.com/kube-logging/telemetry-controller/pkg/resources/otel_conf_gen/pipeline/components"
3334
"github.com/kube-logging/telemetry-controller/pkg/sdk/model"
3435
"github.com/kube-logging/telemetry-controller/pkg/sdk/model/state"
3536
)
@@ -201,7 +202,7 @@ func (t *TenantResourceManager) ValidateSubscriptionOutputs(ctx context.Context,
201202
continue
202203
}
203204

204-
// Ensure the output belongs to the same tenant
205+
// ensure the output belongs to the same tenant
205206
if checkedOutput.Status.Tenant != subscription.Status.Tenant {
206207
t.Error(errors.New("output and subscription tenants mismatch"),
207208
"output and subscription tenants mismatch",
@@ -214,6 +215,16 @@ func (t *TenantResourceManager) ValidateSubscriptionOutputs(ctx context.Context,
214215
continue
215216
}
216217

218+
// validate output secret
219+
if checkedOutput.Spec.Authentication != nil {
220+
if err := components.QueryOutputSecret(ctx, t.Client, checkedOutput); err != nil {
221+
t.Error(err, "failed to query output secret", "output", checkedOutput.NamespacedName().String())
222+
223+
invalidOutputs = append(invalidOutputs, outputRef)
224+
continue
225+
}
226+
}
227+
217228
// update the output state if validation was successful
218229
checkedOutput.SetState(state.StateReady)
219230
if updateErr := t.Status().Update(ctx, checkedOutput); updateErr != nil {

pkg/resources/otel_conf_gen/pipeline/components/common.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@
1515
package components
1616

1717
import (
18+
"context"
19+
"errors"
1820
"fmt"
1921
"slices"
2022
"strings"
2123

2224
corev1 "k8s.io/api/core/v1"
25+
"k8s.io/apimachinery/pkg/types"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
2327

2428
"github.com/kube-logging/telemetry-controller/api/telemetry/v1alpha1"
2529
)
@@ -106,6 +110,60 @@ func (r *ResourceRelations) GetTenantByName(tenantName string) (*v1alpha1.Tenant
106110
return nil, fmt.Errorf("tenant %s not found", tenantName)
107111
}
108112

113+
// QueryOutputSecret retrieves the secret associated with the output's authentication configuration.
114+
// It returns an error if the output is nil, if no authentication is configured,
115+
// if multiple authentication methods are configured, or if the secret cannot be found.
116+
func QueryOutputSecret(ctx context.Context, client client.Client, output *v1alpha1.Output) error {
117+
_, err := QueryOutputSecretWithData(ctx, client, output)
118+
return err
119+
}
120+
121+
// QueryOutputSecretWithData retrieves the secret associated with the output's authentication configuration.
122+
// It returns the secret and an error if the output is nil, if no authentication is configured,
123+
// if multiple authentication methods are configured, or if the secret cannot be found.
124+
func QueryOutputSecretWithData(ctx context.Context, client client.Client, output *v1alpha1.Output) (*corev1.Secret, error) {
125+
auth := output.Spec.Authentication
126+
authCount := 0
127+
if auth.BasicAuth != nil && auth.BasicAuth.SecretRef != nil {
128+
authCount++
129+
}
130+
if auth.BearerAuth != nil && auth.BearerAuth.SecretRef != nil {
131+
authCount++
132+
}
133+
switch authCount {
134+
case 0:
135+
return nil, errors.New("no valid authentication method configured")
136+
case 1:
137+
// Proceed with the single authentication method
138+
default:
139+
return nil, errors.New("multiple authentication methods configured; only one is allowed")
140+
}
141+
142+
var namespacedName types.NamespacedName
143+
var authType string
144+
if auth.BasicAuth != nil && auth.BasicAuth.SecretRef != nil {
145+
namespacedName = types.NamespacedName{
146+
Namespace: auth.BasicAuth.SecretRef.Namespace,
147+
Name: auth.BasicAuth.SecretRef.Name,
148+
}
149+
authType = "BasicAuth"
150+
} else if auth.BearerAuth != nil && auth.BearerAuth.SecretRef != nil {
151+
namespacedName = types.NamespacedName{
152+
Namespace: auth.BearerAuth.SecretRef.Namespace,
153+
Name: auth.BearerAuth.SecretRef.Name,
154+
}
155+
authType = "BearerAuth"
156+
}
157+
158+
var secret *corev1.Secret
159+
if err := client.Get(ctx, namespacedName, secret); err != nil {
160+
return nil, fmt.Errorf("failed to retrieve %s secret %s/%s: %w",
161+
authType, namespacedName.Namespace, namespacedName.Name, err)
162+
}
163+
164+
return secret, nil
165+
}
166+
109167
func SortNamespacedNames(names []v1alpha1.NamespacedName) {
110168
slices.SortFunc(names, func(a, b v1alpha1.NamespacedName) int {
111169
return strings.Compare(a.String(), b.String())

0 commit comments

Comments
 (0)