Skip to content

Commit

Permalink
Add Vault, KVDB, and Docker auth token_secret support (#1196)
Browse files Browse the repository at this point in the history
Signed-off-by: Grant Griffiths <[email protected]>
  • Loading branch information
ggriffiths authored Aug 20, 2019
1 parent 11e9383 commit 47fae40
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 184 deletions.
8 changes: 8 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
Name = "name"
Token = "token"
TokenSecret = "token_secret"
TokenSecretNamespace = "token_secret_namespace"
SpecNodes = "nodes"
SpecParent = "parent"
SpecEphemeral = "ephemeral"
Expand Down Expand Up @@ -1141,3 +1142,10 @@ func (m *VolumeStateAction) IsMount() bool {
func (m *VolumeStateAction) IsUnMount() bool {
return m.GetMount() == VolumeActionParam_VOLUME_ACTION_PARAM_OFF
}

// TokenSecretContext contains all nessesary information to get a
// token secret from any provider
type TokenSecretContext struct {
SecretName string
SecretNamespace string
}
12 changes: 6 additions & 6 deletions api/server/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type driver struct {
conn *grpc.ClientConn
mu sync.Mutex

secretsStore osecrets.Auth
secretsStore *osecrets.Auth
}

type handshakeResp struct {
Expand Down Expand Up @@ -78,7 +78,7 @@ type capabilitiesResponse struct {
Capabilities capabilities
}

func newVolumePlugin(name, sdkUds string, secretsStore osecrets.Auth) restServer {
func newVolumePlugin(name, sdkUds string, secretsStore *osecrets.Auth) restServer {
d := &driver{
restBase: restBase{name: name, version: "0.3"},
SpecHandler: spec.NewSpecHandler(),
Expand Down Expand Up @@ -263,9 +263,9 @@ func (d *driver) parseTokenInput(name string, opts map[string]string) (string, e
}

// get token secret
secretPath, ok := d.GetTokenSecretFromString(name)
tokenSecretContext, ok := d.GetTokenSecretContextFromString(name)
if ok {
token, err := d.secretTokenFromStore(secretPath)
token, err := d.secretTokenFromStore(tokenSecretContext)
if err != nil {
return "", err
}
Expand All @@ -292,12 +292,12 @@ func addTokenMetadata(ctx context.Context, token string) context.Context {

// secretTokenFromStore pulls the token from the configured secret store for
// a given secret name and context.
func (d *driver) secretTokenFromStore(secret string) (string, error) {
func (d *driver) secretTokenFromStore(req *api.TokenSecretContext) (string, error) {
if d.secretsStore == nil {
return "", fmt.Errorf("A secret was passed in, but no secrets provider has been initialized")
}

token, err := d.secretsStore.GetToken(secret, "")
token, err := d.secretsStore.GetToken(req)
if err != nil {
return "", err
}
Expand Down
36 changes: 22 additions & 14 deletions api/server/middleware_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func NewAuthMiddleware(
}

type authMiddleware struct {
provider secrets.Auth
provider *secrets.Auth
}

func (a *authMiddleware) createWithAuth(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
Expand All @@ -67,14 +67,14 @@ func (a *authMiddleware) createWithAuth(w http.ResponseWriter, r *http.Request,

spec := dcReq.GetSpec()
locator := dcReq.GetLocator()
secretName, secretContext, err := a.parseSecret(spec.VolumeLabels, locator.VolumeLabels, true)
tokenSecretContext, err := a.parseSecret(spec.VolumeLabels, locator.VolumeLabels, true)
if err != nil {
a.log(locator.Name, fn).WithError(err).Error("failed to parse secret")
dcRes.VolumeResponse = &api.VolumeResponse{Error: "failed to parse secret: " + err.Error()}
json.NewEncoder(w).Encode(&dcRes)
return
}
if secretName == "" {
if tokenSecretContext.SecretName == "" {
errorMessage := "Access denied, no secret found in the annotations of the persistent volume claim" +
" or storage class parameters"
a.log(locator.Name, fn).Error(errorMessage)
Expand All @@ -84,7 +84,7 @@ func (a *authMiddleware) createWithAuth(w http.ResponseWriter, r *http.Request,
return
}

token, err := a.provider.GetToken(secretName, secretContext)
token, err := a.provider.GetToken(tokenSecretContext)
if err != nil {
a.log(locator.Name, fn).WithError(err).Error("failed to get token")
dcRes.VolumeResponse = &api.VolumeResponse{Error: "failed to get token: " + err.Error()}
Expand Down Expand Up @@ -213,14 +213,14 @@ func (a *authMiddleware) deleteWithAuth(w http.ResponseWriter, r *http.Request,
}

volumeResponse := &api.VolumeResponse{}
secretName, secretContext, err := a.parseSecret(vols[0].Spec.VolumeLabels, vols[0].Locator.VolumeLabels, false)
tokenSecretContext, err := a.parseSecret(vols[0].Spec.VolumeLabels, vols[0].Locator.VolumeLabels, false)
if err != nil {
a.log(volumeID, fn).WithError(err).Error("failed to parse secret")
volumeResponse.Error = "failed to parse secret: " + err.Error()
json.NewEncoder(w).Encode(volumeResponse)
return
}
if secretName == "" {
if tokenSecretContext.SecretName == "" {
errorMessage := fmt.Sprintf("Error, unable to get secret information from the volume."+
" You may need to re-add the following keys as volume labels to point to the secret: %s and %s",
secrets.SecretNameKey, secrets.SecretNamespaceKey)
Expand All @@ -231,7 +231,7 @@ func (a *authMiddleware) deleteWithAuth(w http.ResponseWriter, r *http.Request,
return
}

token, err := a.provider.GetToken(secretName, secretContext)
token, err := a.provider.GetToken(tokenSecretContext)
if err != nil {
a.log(volumeID, fn).WithError(err).Error("failed to get token")
volumeResponse.Error = "failed to get token: " + err.Error()
Expand Down Expand Up @@ -315,7 +315,7 @@ func (a *authMiddleware) parseParam(r *http.Request, param string) (string, erro
func (a *authMiddleware) parseSecret(
specLabels, locatorLabels map[string]string,
fetchCOLabels bool,
) (string, string, error) {
) (*api.TokenSecretContext, error) {
if a.provider.Type() == secrets.TypeK8s && fetchCOLabels {
// For k8s fetch the actual annotations
pvcName, ok := locatorLabels[PVCNameLabelKey]
Expand All @@ -331,33 +331,41 @@ func (a *authMiddleware) parseSecret(

pvc, err := k8s.Instance().GetPersistentVolumeClaim(pvcName, pvcNamespace)
if err != nil {
return "", "", err
return nil, err
}
secretName := pvc.ObjectMeta.Annotations[secrets.SecretNameKey]
secretNamespace := pvc.ObjectMeta.Annotations[secrets.SecretNamespaceKey]

if len(secretName) == 0 {
return parseSecretFromLabels(specLabels, locatorLabels)
}
secretNamespace := pvc.ObjectMeta.Annotations[secrets.SecretNamespaceKey]

return secretName, secretNamespace, nil
return &api.TokenSecretContext{
SecretName: secretName,
SecretNamespace: secretNamespace,
}, nil
}
return parseSecretFromLabels(specLabels, locatorLabels)
}

func parseSecretFromLabels(specLabels, locatorLabels map[string]string) (string, string, error) {
func parseSecretFromLabels(specLabels, locatorLabels map[string]string) (*api.TokenSecretContext, error) {
// Locator labels take precendence
secretName := locatorLabels[secrets.SecretNameKey]
secretNamespace := locatorLabels[secrets.SecretNamespaceKey]
if secretName == "" {
secretName = specLabels[secrets.SecretNameKey]
}
if secretName == "" {
return "", "", fmt.Errorf("secret name is empty")
return nil, fmt.Errorf("secret name is empty")
}
if secretNamespace == "" {
secretNamespace = specLabels[secrets.SecretNamespaceKey]
}
return secretName, secretNamespace, nil

return &api.TokenSecretContext{
SecretName: secretName,
SecretNamespace: secretNamespace,
}, nil
}

func (a *authMiddleware) log(id, fn string) *logrus.Entry {
Expand Down
2 changes: 1 addition & 1 deletion api/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func StartVolumePluginAPI(
authProviderType secrets.AuthTokenProviders,
authProvider osecrets.Secrets,
) error {
var secretsStore secrets.Auth
var secretsStore *secrets.Auth
var err error

// Only initialize secrets store if we have a valid auth provider.
Expand Down
25 changes: 16 additions & 9 deletions api/spec/spec_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ type SpecHandler interface {
// ("", false)
GetTokenFromString(str string) (string, bool)

// GetTokenSecretFromString parses the full token secret path from the name.
// If the token was parsed, it returns:
// (tokenSecret, true)
// If the token wasn't parsed, it returns:
// ("", false)
GetTokenSecretFromString(str string) (string, bool)
// GetTokenSecretContextFromString parses the full token secret request from the name.
// If the token secret was parsed, it returns:
// (tokenSecretContext, true)
// If the token secret wasn't parsed, it returns:
// (nil, false)
GetTokenSecretContextFromString(str string) (*api.TokenSecretContext, bool)

// SpecFromOpts parses in docker options passed in the the docker run
// command of the form --opt name=value
Expand Down Expand Up @@ -95,6 +95,7 @@ var (
nameRegex = regexp.MustCompile(api.Name + "=([0-9A-Za-z_-]+),?")
tokenRegex = regexp.MustCompile(api.Token + "=([A-Za-z0-9-_=]+\\.[A-Za-z0-9-_=]+\\.?[A-Za-z0-9-_.+/=]+),?")
tokenSecretRegex = regexp.MustCompile(api.TokenSecret + `=/*([0-9A-Za-z_/]+),?`)
tokenSecretNamespaceRegex = regexp.MustCompile(api.TokenSecretNamespace + `=/*([0-9A-Za-z_/]+),?`)
nodesRegex = regexp.MustCompile(api.SpecNodes + "=([A-Za-z0-9-_;]+),?")
parentRegex = regexp.MustCompile(api.SpecParent + "=([A-Za-z]+),?")
sizeRegex = regexp.MustCompile(api.SpecSize + "=([0-9A-Za-z]+),?")
Expand Down Expand Up @@ -393,15 +394,21 @@ func (d *specHandler) GetTokenFromString(str string) (string, bool) {
return token, ok
}

func (d *specHandler) GetTokenSecretFromString(str string) (string, bool) {
func (d *specHandler) GetTokenSecretContextFromString(str string) (*api.TokenSecretContext, bool) {
submatches := tokenSecretRegex.FindStringSubmatch(str)
if len(submatches) < 2 {
return "", false
// Must at least have a secret name. All other fields are optional,
// depending on the secrets provider configured.
return nil, false
}
secret := submatches[1]
secret = strings.TrimRight(secret, "/")

return secret, true
_, secretNamespace := d.getVal(tokenSecretNamespaceRegex, str)
return &api.TokenSecretContext{
SecretName: secret,
SecretNamespace: secretNamespace,
}, true
}

func (d *specHandler) SpecOptsFromString(
Expand Down
43 changes: 26 additions & 17 deletions api/spec/spec_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,38 +239,47 @@ func TestGetTokenFromString(t *testing.T) {

}

func TestGetTokenSecretFromString(t *testing.T) {
func TestGetTokenSecretContextFromString(t *testing.T) {
s := NewSpecHandler()

tt := []struct {
InputSecret string
ExpectedSecret string
Successful bool
InputName string
ExpectedRequest api.TokenSecretContext
Successful bool
}{
{
"/px/secrets/alpha/token1",
"px/secrets/alpha/token1",
"name=abcd,token_secret=/px/secrets/alpha/token1," +
"token_secret_namespace=ns2,abcd=sd,token_secret_public_data=y," +
"token_secret_custom_data=n",
api.TokenSecretContext{
SecretName: "px/secrets/alpha/token1",
SecretNamespace: "ns2",
},
true,
}, {
"abcd/secrets/alpha//token1/",
"abcd/secrets/alpha//token1",
"name=abcd,token_secret=abcd/secrets/alpha//token1/",
api.TokenSecretContext{
SecretName: "abcd/secrets/alpha//token1",
SecretNamespace: "",
},
true,
}, {
"simplekey",
"simplekey",
"name=abcd,token_secret=simplekey",
api.TokenSecretContext{
SecretName: "simplekey",
SecretNamespace: "",
},
true,
},
}

for _, tc := range tt {
str := "name=abcd,token_secret=" + tc.InputSecret + ",token="
secretParsed, ok := s.GetTokenSecretFromString(str)
require.Equal(t, tc.ExpectedSecret, secretParsed)
require.Equal(t, ok, tc.Successful)
secretParsed, ok := s.GetTokenSecretContextFromString(tc.InputName)
require.Equal(t, tc.Successful, ok)
require.Equal(t, tc.ExpectedRequest, *secretParsed)
}

secretParsed, ok := s.GetTokenSecretFromString(fmt.Sprintf("toabcbn_secret=abcd"))
require.Equal(t, "", secretParsed)
require.Equal(t, ok, false)
_, ok := s.GetTokenSecretContextFromString(fmt.Sprintf("toabcbn_secret=abcd"))
require.Equal(t, false, ok)

}
Loading

0 comments on commit 47fae40

Please sign in to comment.