config/config.go (132 lines of code) (raw):
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package config includes helpers for parsing and accessing the information
// from the secrets CSI driver mount events.
package config
import (
"encoding/json"
"errors"
"fmt"
"os"
"github.com/GoogleCloudPlatform/secrets-store-csi-driver-provider-gcp/vars"
"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog/v2"
)
const (
attributePodName = "csi.storage.k8s.io/pod.name"
attributePodNamespace = "csi.storage.k8s.io/pod.namespace"
attributePodUID = "csi.storage.k8s.io/pod.uid"
attributeServiceAccountName = "csi.storage.k8s.io/serviceAccount.name"
attributeServiceAccountTokens = "csi.storage.k8s.io/serviceAccount.tokens" //#nosec G101 -- This is a false positive. Token value is not being revealed. This is just the key name.
)
// Secret holds the parameters of the SecretProviderClass CRD. Links the GCP
// secret resource name to a path in the filesystem.
type Secret struct {
// ResourceName refers to a SecretVersion in the format
// projects/*/secrets/*/versions/*.
ResourceName string `json:"resourceName" yaml:"resourceName"`
// FileName is where the contents of the secret are to be written.
FileName string `json:"fileName" yaml:"fileName"`
// Path is the relative path where the contents of the secret are written.
Path string `json:"path" yaml:"path"`
ExtractJSONKey string `json:"extractJSONKey" yaml:"extractJSONKey"`
// Mode is the optional file mode for the file containing the secret. Must be
// an octal value between 0000 and 0777 or a decimal value between 0 and 511
Mode *int32 `json:"mode,omitempty" yaml:"mode,omitempty"`
}
// PodInfo includes details about the pod that is receiving the mount event.
type PodInfo struct {
Namespace string
Name string
UID types.UID
ServiceAccount string
ServiceAccountTokens string
}
// MountConfig holds the parsed information from a mount event.
type MountConfig struct {
Secrets []*Secret
PodInfo *PodInfo
TargetPath string
Permissions os.FileMode
// AuthPodADC identifies whether Workload Identity should be used for
// authentication. This is the of the pod for volume mount (default)
AuthPodADC bool
// AuthProviderADC identifies whether the Application Default Credentials of the
// GCP Provider DaemonSet should be used for authentication.
// https://cloud.google.com/docs/authentication/production#automatically
AuthProviderADC bool
// AuthNodePublishSecret identifies whether the a K8s Secret provided on the
// NodePublish call should be used for authentication.
// https://kubernetes-csi.github.io/docs/secrets-and-credentials-storage-class.html
//
// If set then AuthKubeSecret will contain the json representation of the
// Google credential (parseable by google.CredentialsFromJSON).
AuthNodePublishSecret bool
AuthKubeSecret []byte
}
// MountParams hold unparsed arguments from the CSI Driver from the mount event.
type MountParams struct {
Attributes string
KubeSecrets string
TargetPath string
Permissions os.FileMode
}
// PathString returns either the FileName or Path parameter of the Secret.
func (s *Secret) PathString() string {
if s.Path != "" {
return s.Path
}
return s.FileName
}
// Parse parses the input MountParams to the more structured MountConfig.
func Parse(in *MountParams) (*MountConfig, error) {
out := &MountConfig{}
out.Permissions = in.Permissions
out.TargetPath = in.TargetPath
out.Secrets = make([]*Secret, 0)
var attrib, secret map[string]string
// Everything in the "parameters" section of the SecretProviderClass.
if err := json.Unmarshal([]byte(in.Attributes), &attrib); err != nil {
return nil, fmt.Errorf("failed to unmarshal attributes: %v", err)
}
out.PodInfo = &PodInfo{
Name: attrib[attributePodName],
Namespace: attrib[attributePodNamespace],
UID: types.UID(attrib[attributePodUID]),
ServiceAccount: attrib[attributeServiceAccountName],
ServiceAccountTokens: attrib[attributeServiceAccountTokens],
}
podInfo := klog.ObjectRef{Namespace: out.PodInfo.Namespace, Name: out.PodInfo.Name}
// The secrets here are the relevant CSI driver (k8s) secrets. See
// https://kubernetes-csi.github.io/docs/secrets-and-credentials-storage-class.html
allowSecretRef, err := vars.AllowNodepublishSeretRef.GetBooleanValue()
if err != nil {
klog.ErrorS(err, "failed to get ALLOW_NODE_PUBLISH_SECRET flag")
klog.Fatal("failed to get ALLOW_NODE_PUBLISH_SECRET flag")
}
if allowSecretRef {
if err := json.Unmarshal([]byte(in.KubeSecrets), &secret); err != nil {
return nil, fmt.Errorf("failed to unmarshal secrets: %v", err)
}
if _, ok := secret["key.json"]; ok {
out.AuthNodePublishSecret = true
out.AuthKubeSecret = []byte(secret["key.json"])
}
} else {
klog.Infoln("Authentication using nodepublishsecret ref is disabled")
}
switch attrib["auth"] {
case "provider-adc":
if out.AuthNodePublishSecret {
klog.InfoS("attempting to set both nodePublishSecretRef and provider-adc auth. For details consult https://github.com/GoogleCloudPlatform/secrets-store-csi-driver-provider-gcp/blob/main/docs/authentication.md", "pod", podInfo)
return nil, fmt.Errorf("attempting to set both nodePublishSecretRef and provider-adc auth")
}
out.AuthProviderADC = true
case "pod-adc":
if out.AuthNodePublishSecret {
klog.InfoS("attempting to set both nodePublishSecretRef and pod-adc auth. For details consult https://github.com/GoogleCloudPlatform/secrets-store-csi-driver-provider-gcp/blob/main/docs/authentication.md", "pod", podInfo)
return nil, fmt.Errorf("attempting to set both nodePublishSecretRef and pod-adc auth")
}
out.AuthPodADC = true
case "":
// default to pod auth unless nodePublishSecret is set
out.AuthPodADC = !out.AuthNodePublishSecret
default:
klog.InfoS("unknown auth configuration", "pod", podInfo)
return nil, fmt.Errorf("unknown auth configuration: %q", attrib["auth"])
}
if out.AuthNodePublishSecret {
klog.V(3).InfoS("parsed auth", "auth", "nodePublishSecretRef", "pod", podInfo)
}
if out.AuthPodADC {
klog.V(3).InfoS("parsed auth", "auth", "pod-adc", "pod", podInfo)
}
if out.AuthProviderADC {
klog.V(3).InfoS("parsed auth", "auth", "provider-adc", "pod", podInfo)
}
if os.Getenv("DEBUG") == "true" {
klog.V(5).InfoS(fmt.Sprintf("attributes: %v", attrib), "pod", podInfo)
klog.V(5).InfoS(fmt.Sprintf("secrets: %v", secret), "pod", podInfo)
} else {
klog.V(5).InfoS("attributes: REDACTED (envvar DEBUG=true to see values)", "pod", podInfo)
klog.V(5).InfoS("secrets: REDACTED (envvar DEBUG=true to see values)", "pod", podInfo)
}
klog.V(5).InfoS(fmt.Sprintf("filePermission: %v", in.Permissions), "pod", podInfo)
klog.V(5).InfoS(fmt.Sprintf("targetPath: %v", in.TargetPath), "pod", podInfo)
if _, ok := attrib["secrets"]; !ok {
return nil, errors.New("missing required 'secrets' attribute")
}
if err := yaml.Unmarshal([]byte(attrib["secrets"]), &out.Secrets); err != nil {
return nil, fmt.Errorf("failed to unmarshal secrets attribute: %v", err)
}
return out, nil
}