Compare commits

...

2 Commits

Author SHA1 Message Date
Jeff McCune
4c2bc34d58 (#32) SecretStore Component
Separate the SecretStore resources from the namespaces component because
it creates a deadlock.  The secretstore crds don't get applied until the
eso component is managed.

The namespaces component should have nothing but core api objects, no
custom resources.
2024-03-07 16:01:22 -08:00
Jeff McCune
d831070f53 Trim trailing newlines from files when creating secrets
Without this patch, the pattern of echoing data (without -n) or editing
files in a directory to represent the keys of a secret results in a
trailing newline in the kubernetes Secret.

This patch trims off the trailing newline by default, with the option to
preserve it with the --trim-trailing-newlines=false flag.
2024-03-06 11:21:32 -08:00
25 changed files with 106 additions and 31 deletions

View File

@@ -2,6 +2,15 @@ package holos
import "encoding/json"
#DependsOn: _ESO
#InputKeys: {
project: "secrets"
component: "eso-creds-refresher"
}
#TargetNamespace: #CredsRefresher.namespace
// output kubernetes api objects for holos
#KubernetesObjects & {
apiObjects: {
@@ -13,15 +22,6 @@ import "encoding/json"
}
}
#InputKeys: {
project: "secrets"
component: "eso-creds-refresher"
}
#TargetNamespace: #CredsRefresher.namespace
#DependsOn: Namespaces: name: #InstancePrefix + "-namespaces"
let NAME = #CredsRefresher.name
let AUD = "//iam.googleapis.com/projects/\(#InputKeys.gcpProjectNumber)/locations/global/workloadIdentityPools/holos/providers/k8s-\(#InputKeys.cluster)"
let MOUNT = "/var/run/service-account"

View File

@@ -0,0 +1,12 @@
package holos
// Components under this directory are part of this collection
#InputKeys: project: "secrets"
// Shared dependencies for all components in this collection.
#DependsOn: _Namespaces
// Common Dependencies
_Namespaces: Namespaces: name: "\(#StageName)-secrets-namespaces"
_ESO: ESO: name: "\(#InstancePrefix)-eso"
_ESOCreds: ESOCreds: name: "\(#InstancePrefix)-eso-creds-refresher"

View File

@@ -0,0 +1,34 @@
package holos
#DependsOn: _ESOCreds
#TargetNamespace: "default"
#InputKeys: {
project: "secrets"
component: "stores"
}
// #PlatformNamespaceObjects defines the api objects necessary for eso SecretStores in external clusters to access secrets in a given namespace in the provisioner cluster.
#PlatformNamespaceObjects: {
_ns: #PlatformNamespace
objects: [
#SecretStore & {
_namespace: _ns.name
},
]
}
#KubernetesObjects & {
apiObjects: {
for ns in #PlatformNamespaces {
for obj in (#PlatformNamespaceObjects & {_ns: ns}).objects {
let Kind = obj.kind
let NS = ns.name
let Name = obj.metadata.name
"\(Kind)": "\(NS)/\(Name)": obj
}
}
}
}

View File

@@ -9,7 +9,7 @@ package holos
component: "validate"
}
#DependsOn: Namespaces: name: #InstancePrefix + "-eso"
#DependsOn: _ESO
#KubernetesObjects & {
apiObjects: {

View File

@@ -133,9 +133,12 @@ This section configured:
1. Provisioner Cluster to provide secrets to workload clusters.
2. IAM service account `eso-creds-refresher` to identify the credential refresher job.
3. Workload identity pool to authenticate the `eso-creds-refresher` Kubernetes service account in an external cluster.
4. IAM policy to allow `eso-creds-refresher` to authenticate to the Provisioner Cluster.
5. RoleBinding to allow `eso-creds-refresher` to create kubernetes service account tokens representing the credentials for use by SecretStore resources in workload clusters.
3. Workload identity pool to authenticate the `system:serviceaccount:holos-system:eso-creds-refresher` Kubernetes service account in all clusters that share the workload identity pool.
4. IAM policy to allow the `eso-creds-refresher` IAM service account to authenticate to the Provisioner Cluster.
5. RoleBinding to allow the `eso-creds-refresher` IAM service account to create kubernetes service account tokens representing the credentials for use by SecretStore resources in workload clusters.
> [!NOTE]
> Any cluster in the workload identity pool can impersonate the eso-creds-refresher IAM service account.
## Cluster Setup
@@ -150,6 +153,12 @@ HOLOS_CLUSTER_NAME=west1
ISSUER_URL="https://example.com/clusters/${HOLOS_CLUSTER_NAME}"
```
Alternatively:
```shell
ISSUER_URL="$(kubectl get --raw='/.well-known/openid-configuration' | jq -r .issuer)"
```
```shell
gcloud iam workload-identity-pools providers create-oidc \
k8s-$HOLOS_CLUSTER_NAME \

View File

@@ -1,6 +1,7 @@
package secret
import (
"bytes"
"fmt"
"github.com/holos-run/holos/pkg/cli/command"
"github.com/holos-run/holos/pkg/holos"
@@ -29,6 +30,7 @@ func NewCreateCmd(hc *holos.Config) *cobra.Command {
cfg.dryRun = flagSet.Bool("dry-run", false, "dry run")
cfg.appendHash = flagSet.Bool("append-hash", true, "append hash to kubernetes secret name")
cfg.dataStdin = flagSet.Bool("data-stdin", false, "read data field as json from stdin if")
cfg.trimTrailingNewlines = flagSet.Bool("trim-trailing-newlines", true, "trim trailing newlines if true")
cmd.Flags().SortFlags = false
cmd.Flags().AddGoFlagSet(flagSet)
@@ -80,7 +82,7 @@ func makeCreateRunFunc(hc *holos.Config, cfg *config) command.RunFunc {
}
for _, file := range cfg.files {
if err := filepath.WalkDir(file, makeWalkFunc(secret.Data, file)); err != nil {
if err := filepath.WalkDir(file, makeWalkFunc(secret.Data, file, *cfg.trimTrailingNewlines)); err != nil {
return wrapper.Wrap(err)
}
}
@@ -125,7 +127,7 @@ func makeCreateRunFunc(hc *holos.Config, cfg *config) command.RunFunc {
}
}
func makeWalkFunc(data secretData, root string) fs.WalkDirFunc {
func makeWalkFunc(data secretData, root string, trimNewlines bool) fs.WalkDirFunc {
return func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
@@ -143,6 +145,9 @@ func makeWalkFunc(data secretData, root string) fs.WalkDirFunc {
if data[key], err = os.ReadFile(path); err != nil {
return wrapper.Wrap(err)
}
if trimNewlines {
data[key] = bytes.TrimRight(data[key], "\r\n")
}
}
return nil

View File

@@ -12,15 +12,16 @@ const ClusterLabel = "holos.run/cluster.name"
type secretData map[string][]byte
type config struct {
files holos.StringSlice
printFile *string
extract *bool
dryRun *bool
appendHash *bool
dataStdin *bool
cluster *string
namespace *string
extractTo *string
files holos.StringSlice
printFile *string
extract *bool
dryRun *bool
appendHash *bool
dataStdin *bool
trimTrailingNewlines *bool
cluster *string
namespace *string
extractTo *string
}
func newConfig() (*config, *flag.FlagSet) {

View File

@@ -1,5 +1,5 @@
# Create the secret
holos create secret directory --from-file=$WORK/fixture --dry-run
holos create secret directory --trim-trailing-newlines=false --from-file=$WORK/fixture --dry-run
# Want no warnings.
! stderr 'WRN'

View File

@@ -1,5 +1,5 @@
# Create the secret
holos create secret directory --from-file=$WORK/want
holos create secret directory --trim-trailing-newlines=false --from-file=$WORK/want
stderr 'created: directory-..........'
stderr 'secret=directory-..........'
stderr 'name=directory'

View File

@@ -0,0 +1,17 @@
# Create a secret from files with trailing newlines
holos create secret smtp --from-file=$WORK/smtp
# Get the secret back expecting no trailing newlines
mkdir have
holos get secret smtp
stdout '"username": "holos.run@gmail.com"'
stdout '"password": "secret"'
-- smtp/username --
holos.run@gmail.com
-- smtp/password --
secret
-- smtp/host --
smtp.gmail.com
-- smtp/port --
587

View File

@@ -1,5 +1,5 @@
# Create the secret
holos create secret directory --from-file=$WORK/want
holos create secret directory --trim-trailing-newlines=false --from-file=$WORK/want
# Get the secret back
mkdir have

View File

@@ -1 +1 @@
2
4