Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report secret version as a hash of the input and contents #148

Merged
merged 6 commits into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
## Unreleased

CHANGES:

* Duplicate object names now trigger an error instead of silently overwriting files. [[GH-148](https://github.com/hashicorp/vault-csi-provider/pull/148)]

IMPROVEMENTS:

* Secret versions are now reported as a hash of their input and contents instead of hardcoded to 0. [[GH-148](https://github.com/hashicorp/vault-csi-provider/pull/148)]

## 1.1.0 (April 26th, 2022)

IMPROVEMENTS:
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
Expand All @@ -174,6 +175,7 @@ github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq
github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk=
github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
Expand Down Expand Up @@ -476,6 +478,7 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.5/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
Expand All @@ -491,13 +494,15 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ=
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
Expand Down Expand Up @@ -530,6 +535,7 @@ github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down Expand Up @@ -1018,6 +1024,7 @@ gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
Expand Down Expand Up @@ -1071,6 +1078,7 @@ k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/klog/v2 v2.10.0 h1:R2HDMDJsHVTHA2n4RjwbeYXdOcBymXdX/JRb1v0VGhE=
k8s.io/klog/v2 v2.10.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
k8s.io/mount-utils v0.21.0/go.mod h1:dwXbIPxKtTjrBEaX1aK/CMEf1KZ8GzMHpe3NEBfdFXI=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
Expand Down
17 changes: 17 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package config
import (
"encoding/json"
"errors"
"fmt"
"os"
"strconv"
"strings"

"github.com/hashicorp/vault/api"
"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -161,5 +163,20 @@ func (c *Config) validate() error {
return errors.New("no secrets configured - the provider will not read any secret material")
}

objectNames := map[string]struct{}{}
conflicts := []string{}
for _, secret := range c.Parameters.Secrets {
if _, exists := objectNames[secret.ObjectName]; exists {
conflicts = append(conflicts, secret.ObjectName)
}

objectNames[secret.ObjectName] = struct{}{}
}

if len(conflicts) > 0 {
return fmt.Errorf("each `objectName` within a SecretProviderClass must be unique, "+
"but the following keys were duplicated: %s", strings.Join(conflicts, ", "))
}

return nil
}
14 changes: 13 additions & 1 deletion internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"encoding/json"
"io/ioutil"
"net/http"
"path/filepath"
"testing"

Expand Down Expand Up @@ -92,7 +93,7 @@ func TestParseParameters(t *testing.T) {
Insecure: true,
},
Secrets: []Secret{
{"bar1", "v1/secret/foo1", "", "GET", nil, 0},
{"bar1", "v1/secret/foo1", "", http.MethodGet, nil, 0},
{"bar2", "v1/secret/foo2", "", "", nil, 0},
},
PodInfo: PodInfo{
Expand Down Expand Up @@ -268,6 +269,17 @@ func TestValidateConfig(t *testing.T) {
return cfg
}(),
},
{
name: "Duplicate objectName",
cfg: func() Config {
cfg := minimumValid
cfg.Parameters.Secrets = []Secret{
{ObjectName: "foo", SecretPath: "path/one"},
{ObjectName: "foo", SecretPath: "path/two"},
}
return cfg
}(),
},
} {
err := tc.cfg.validate()
if tc.cfgValid {
Expand Down
97 changes: 38 additions & 59 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package provider

import (
"context"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"

Expand All @@ -18,7 +18,6 @@ import (
authenticationv1 "k8s.io/api/authentication/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
pb "sigs.k8s.io/secrets-store-csi-driver/provider/v1alpha1"
)

Expand All @@ -27,12 +26,16 @@ import (
type provider struct {
logger hclog.Logger
cache map[cacheKey]*api.Secret

// Allows mocking Kubernetes API for tests.
k8sClient kubernetes.Interface
}

func NewProvider(logger hclog.Logger) *provider {
func NewProvider(logger hclog.Logger, k8sClient kubernetes.Interface) *provider {
p := &provider{
logger: logger,
cache: make(map[cacheKey]*api.Secret),
logger: logger,
cache: make(map[cacheKey]*api.Secret),
k8sClient: k8sClient,
}

return p
Expand All @@ -50,21 +53,12 @@ func (p *provider) createJWTToken(ctx context.Context, podInfo config.PodInfo, a
"podName", podInfo.Name,
"podUID", podInfo.UID)

config, err := rest.InClusterConfig()
if err != nil {
return "", err
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return "", err
}

ttl := int64((15 * time.Minute).Seconds())
audiences := []string{}
if audience != "" {
audiences = []string{audience}
}
resp, err := clientset.CoreV1().ServiceAccounts(podInfo.Namespace).CreateToken(ctx, podInfo.ServiceAccountName, &authenticationv1.TokenRequest{
resp, err := p.k8sClient.CoreV1().ServiceAccounts(podInfo.Namespace).CreateToken(ctx, podInfo.ServiceAccountName, &authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{
ExpirationSeconds: &ttl,
Audiences: audiences,
Expand All @@ -84,31 +78,31 @@ func (p *provider) createJWTToken(ctx context.Context, podInfo config.PodInfo, a
return resp.Status.Token, nil
}

func (p *provider) login(ctx context.Context, client *api.Client, params config.Parameters) (string, error) {
func (p *provider) login(ctx context.Context, client *api.Client, params config.Parameters) error {
p.logger.Debug("performing vault login")

jwt, err := p.createJWTToken(ctx, params.PodInfo, params.Audience)
if err != nil {
return "", err
return err
}

req := client.NewRequest("POST", "/v1/auth/"+params.VaultKubernetesMountPath+"/login")
req := client.NewRequest(http.MethodPost, "/v1/auth/"+params.VaultKubernetesMountPath+"/login")
err = req.SetJSONBody(map[string]string{
"role": params.VaultRoleName,
"jwt": jwt,
})
if err != nil {
return "", err
return err
}
secret, err := vaultclient.Do(ctx, client, req)
if err != nil {
return "", fmt.Errorf("failed to login: %w", err)
return fmt.Errorf("failed to login: %w", err)
}

client.SetToken(secret.Auth.ClientToken)

p.logger.Debug("vault login successful")
return secret.Auth.ClientToken, nil
return nil
}

func ensureV1Prefix(s string) string {
Expand Down Expand Up @@ -136,7 +130,7 @@ func generateRequest(client *api.Client, secret config.Secret) (*api.Request, er
}
secretPath = secretPath[:queryIndex]
}
method := "GET"
method := http.MethodGet
if secret.Method != "" {
method = secret.Method
}
Expand Down Expand Up @@ -231,8 +225,6 @@ func (p *provider) getSecret(ctx context.Context, client *api.Client, secretConf

// MountSecretsStoreObjectContent mounts content of the vault object to target path
func (p *provider) HandleMountRequest(ctx context.Context, cfg config.Config, flagsConfig config.FlagsConfig) (*pb.MountResponse, error) {
versions := make(map[string]string)

client, err := vaultclient.New(cfg.Parameters, flagsConfig)
if err != nil {
return nil, err
Expand All @@ -244,63 +236,50 @@ func (p *provider) HandleMountRequest(ctx context.Context, cfg config.Config, fl
}

// Authenticate to vault using the jwt token
_, err = p.login(ctx, client, cfg.Parameters)
err = p.login(ctx, client, cfg.Parameters)
if err != nil {
return nil, err
}

var files []*pb.File
var objectVersions []*pb.ObjectVersion
for _, secret := range cfg.Parameters.Secrets {
content, err := p.getSecret(ctx, client, secret)
if err != nil {
return nil, err
}
versions[fmt.Sprintf("%s:%s:%s", secret.ObjectName, secret.SecretPath, secret.Method)] = "0"

version, err := generateObjectVersion(secret, content)
if err != nil {
return nil, fmt.Errorf("failed to generate version for object name %q: %w", secret.ObjectName, err)
}

filePermission := int32(cfg.FilePermission)
if secret.FilePermission != 0 {
filePermission = int32(secret.FilePermission)
}
files = append(files, &pb.File{Path: secret.ObjectName, Mode: filePermission, Contents: content})
objectVersions = append(objectVersions, version)
p.logger.Info("secret added to mount response", "directory", cfg.TargetPath, "file", secret.ObjectName)
}

var ov []*pb.ObjectVersion
for k, v := range versions {
ov = append(ov, &pb.ObjectVersion{Id: k, Version: v})
}

return &pb.MountResponse{
ObjectVersion: ov,
Files: files,
ObjectVersion: objectVersions,
}, nil
}

func writeSecret(logger hclog.Logger, directory string, file string, content []byte, permission os.FileMode) error {
if err := validateFilePath(file); err != nil {
return err
}
if filepath.Base(file) != file {
err := os.MkdirAll(filepath.Join(directory, filepath.Dir(file)), 0755)
if err != nil {
return err
}
}
if err := ioutil.WriteFile(filepath.Join(directory, file), content, permission); err != nil {
return fmt.Errorf("secrets-store csi driver failed to write %s at %s: %w", file, directory, err)
}
logger.Info("secrets-store csi driver wrote secret", "directory", directory, "file", file)

return nil
}

func validateFilePath(path string) error {
segments := strings.Split(strings.ReplaceAll(path, `\`, "/"), "/")
for _, segment := range segments {
if segment == ".." {
return fmt.Errorf("ObjectName %q invalid, must not contain any .. segments", path)
}
func generateObjectVersion(secret config.Secret, content []byte) (*pb.ObjectVersion, error) {
hash := sha256.New()
// We include the secret config in the hash input to avoid leaking information
// about different secrets that could have the same content.
_, err := hash.Write([]byte(fmt.Sprintf("%v:%s", secret, content)))
if err != nil {
return nil, err
}

return nil
return &pb.ObjectVersion{
Id: secret.ObjectName,
Version: base64.URLEncoding.EncodeToString(hash.Sum(nil)),
}, nil
}
Loading