feat: load basic auth password from secret

Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
Stavros
2026-09-22 22:40:54 +03:00
co-authored by Codex
parent f98c844f28
commit b29e1bddc5
8 changed files with 106 additions and 28 deletions
+16
View File
@@ -70,6 +70,7 @@ require (
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
@@ -77,6 +78,20 @@ require (
github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect
github.com/go-logr/logr v1.4.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v1.0.0 // indirect
github.com/go-openapi/jsonreference v1.0.0 // indirect
github.com/go-openapi/swag v0.27.1 // indirect
github.com/go-openapi/swag/cmdutils v0.27.1 // indirect
github.com/go-openapi/swag/conv v0.27.1 // indirect
github.com/go-openapi/swag/fileutils v0.27.1 // indirect
github.com/go-openapi/swag/jsonutils v0.27.1 // indirect
github.com/go-openapi/swag/loading v0.27.1 // indirect
github.com/go-openapi/swag/mangling v0.27.1 // indirect
github.com/go-openapi/swag/netutils v0.27.1 // indirect
github.com/go-openapi/swag/pools v0.27.1 // indirect
github.com/go-openapi/swag/stringutils v0.27.1 // indirect
github.com/go-openapi/swag/typeutils v0.27.1 // indirect
github.com/go-openapi/swag/yamlutils v0.27.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.30.1 // indirect
@@ -142,6 +157,7 @@ require (
golang.org/x/text v0.41.0 // indirect
golang.org/x/time v0.15.0 // indirect
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gotest.tools/v3 v3.5.2 // indirect
k8s.io/klog/v2 v2.140.0 // indirect
+6
View File
@@ -138,6 +138,8 @@ github.com/go-openapi/swag/fileutils v0.27.1 h1:QQqBSoi5mW4XpU85nS0mLcA+zAE6vLzr
github.com/go-openapi/swag/fileutils v0.27.1/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8=
github.com/go-openapi/swag/jsonutils v0.27.1 h1:SVgK3i4USzCU5mibOOS/l4ea2h9UQXy7J7RNLTjuXjU=
github.com/go-openapi/swag/jsonutils v0.27.1/go.mod h1:tdlEpZqdcQ17uj6J4YdK9vd8It5qWMwjWXOs0tjpRlk=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1 h1:mJu3COL9WEaZVp/Kf2PRMi7tPszPEJfSr/OO75ynCs8=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY=
github.com/go-openapi/swag/loading v0.27.1 h1:/DxUgDXKbBX4bcn7r9uEXfJyzN5XpiJmZplzQTjrRCY=
github.com/go-openapi/swag/loading v0.27.1/go.mod h1:jvGh3iA2+zyUUycB5fgJWzeHnhrpvGnJJM0RVE9ZShE=
github.com/go-openapi/swag/mangling v0.27.1 h1:yC9D0HyUE8gbP+BfmGx9+AA89ikwZTMjESK3OnnoaqA=
@@ -152,6 +154,10 @@ github.com/go-openapi/swag/typeutils v0.27.1 h1:KSTdFlfnse4r6dP9IrEnwMldjE+zs71U
github.com/go-openapi/swag/typeutils v0.27.1/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
github.com/go-openapi/swag/yamlutils v0.27.1 h1:ftxv6xvXb1E3zohUc+okZ9nSqNb9StQX/FXnKZ98sQA=
github.com/go-openapi/swag/yamlutils v0.27.1/go.mod h1:bnxFIB1qewGRiZHypXGZ3fNgf13/0HfRgnS/iZBDrOo=
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0=
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo=
github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug=
github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+30 -5
View File
@@ -1,22 +1,29 @@
package service
import (
"context"
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"github.com/tinyauthapp/tinyauth/pkg/apis/tinyauth/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
type KubernetesCRDInput struct {
Log *logger.Logger
Log *logger.Logger
Client kubernetes.Interface
}
type KubernetesCRDExtractor struct {
log *logger.Logger
log *logger.Logger
client kubernetes.Interface
}
func NewKubernetesCRDExtractor(i KubernetesCRDInput) *KubernetesCRDExtractor {
return &KubernetesCRDExtractor{
log: i.Log,
log: i.Log,
client: i.Client,
}
}
@@ -48,11 +55,29 @@ func (k *KubernetesCRDExtractor) Extract(app *v1alpha1.Application) ExtractionRe
}
}
// Convert the CRD to the internal representation.
internalApp := app.Spec.ToInternalApp()
passwordRef := app.Spec.Response.BasicAuth.PasswordSecretRef
if passwordRef != nil {
secret, err := k.client.CoreV1().Secrets(meta.Namespace).Get(context.Background(), passwordRef.Name, metav1.GetOptions{})
if err != nil {
k.log.App.Warn().Err(err).Str("namespace", meta.Namespace).Str("name", meta.Name).Str("secret", passwordRef.Name).Str("key", passwordRef.Key).Msg("Failed to read basic auth password Secret, skipping")
return ExtractionResult{Meta: meta}
}
password, ok := secret.Data[passwordRef.Key]
if !ok {
k.log.App.Warn().Str("namespace", meta.Namespace).Str("name", meta.Name).Str("secret", passwordRef.Name).Str("key", passwordRef.Key).Msg("Basic auth password Secret key does not exist, skipping")
return ExtractionResult{Meta: meta}
}
internalApp.Response.BasicAuth.Password = string(password)
}
return ExtractionResult{
Meta: meta,
Apps: map[string]model.App{
// Convert the CRD to the internal representation
meta.Name: app.Spec.ToInternalApp(),
meta.Name: internalApp,
},
}
}
+16 -8
View File
@@ -18,6 +18,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
@@ -114,10 +115,11 @@ func (ti *typedItem) fromUnstructured(typ ResourceType, obj *unstructured.Unstru
type KubernetesService struct {
log *logger.Logger
apps map[ResourceMeta]map[string]model.App
client dynamic.Interface
mu sync.RWMutex
connected bool
apps map[ResourceMeta]map[string]model.App
client dynamic.Interface
typedClient kubernetes.Interface
mu sync.RWMutex
connected bool
}
type KubernetesServiceInput struct {
@@ -138,11 +140,16 @@ func NewKubernetesService(i KubernetesServiceInput) (*KubernetesService, error)
if err != nil {
return nil, fmt.Errorf("failed to create kubernetes client: %w", err)
}
typedClient, err := kubernetes.NewForConfig(cfg)
if err != nil {
return nil, fmt.Errorf("failed to create typed kubernetes client: %w", err)
}
service := &KubernetesService{
log: i.Log,
client: client,
apps: make(map[ResourceMeta]map[string]model.App),
log: i.Log,
client: client,
typedClient: typedClient,
apps: make(map[ResourceMeta]map[string]model.App),
}
watchedGVRs := make(map[string]bool)
@@ -226,7 +233,8 @@ func (k *KubernetesService) watchedItemChange(res watchedResource, typedItem *ty
return
}
extractor := NewKubernetesCRDExtractor(KubernetesCRDInput{
Log: k.log,
Log: k.log,
Client: k.typedClient,
})
result = extractor.Extract(typedItem.crd)
}
+6 -4
View File
@@ -1,6 +1,9 @@
package v1alpha1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
//go:generate controller-gen object paths=$GOFILE
@@ -81,9 +84,8 @@ type AppResponse struct {
// AppBasicAuth specifies basic auth credentials
type AppBasicAuth struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
PasswordFile string `json:"passwordFile,omitempty"`
Username string `json:"username,omitempty"`
PasswordSecretRef *corev1.SecretKeySelector `json:"passwordSecretRef,omitempty"`
}
// +k8s:deepcopy-gen=true
@@ -48,11 +48,8 @@ spec:
properties:
domain:
type: string
name:
type: string
required:
- domain
- name
type: object
ip:
description: AppIP specifies IP access control rules
@@ -99,10 +96,30 @@ spec:
basicAuth:
description: AppBasicAuth specifies basic auth credentials
properties:
password:
type: string
passwordFile:
type: string
passwordSecretRef:
description: SecretKeySelector selects a key of a Secret.
properties:
key:
description: The key of the secret to select from. Must
be a valid secret key.
type: string
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
x-kubernetes-map-type: atomic
username:
type: string
type: object
+1 -3
View File
@@ -26,9 +26,7 @@ func (s *ApplicationSpec) ToInternalApp() model.App {
Response: model.AppResponse{
Headers: s.Response.Headers,
BasicAuth: model.AppBasicAuth{
Username: s.Response.BasicAuth.Username,
Password: s.Response.BasicAuth.Password,
PasswordFile: s.Response.BasicAuth.PasswordFile,
Username: s.Response.BasicAuth.Username,
},
},
Path: model.AppPath{
@@ -5,12 +5,18 @@
package v1alpha1
import (
"k8s.io/api/core/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AppBasicAuth) DeepCopyInto(out *AppBasicAuth) {
*out = *in
if in.PasswordSecretRef != nil {
in, out := &in.PasswordSecretRef, &out.PasswordSecretRef
*out = new(v1.SecretKeySelector)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppBasicAuth.
@@ -121,7 +127,7 @@ func (in *AppResponse) DeepCopyInto(out *AppResponse) {
*out = make([]string, len(*in))
copy(*out, *in)
}
out.BasicAuth = in.BasicAuth
in.BasicAuth.DeepCopyInto(&out.BasicAuth)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppResponse.