Skip to content

Commit

Permalink
Add support to pass security token through token_secret in DC/OS
Browse files Browse the repository at this point in the history
Signed-off-by: Grant Griffiths <[email protected]>
  • Loading branch information
ggriffiths committed Mar 18, 2019
1 parent fd7e099 commit f349860
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 23 deletions.
1 change: 1 addition & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
const (
Name = "name"
Token = "token"
TokenSecret = "token_secret"
SpecNodes = "nodes"
SpecParent = "parent"
SpecEphemeral = "ephemeral"
Expand Down
115 changes: 93 additions & 22 deletions api/server/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/libopenstorage/openstorage/api"
"github.com/libopenstorage/openstorage/api/spec"
"github.com/libopenstorage/openstorage/config"
osecrets "github.com/libopenstorage/openstorage/pkg/auth/secrets"
"github.com/libopenstorage/openstorage/pkg/grpcserver"
"github.com/libopenstorage/openstorage/pkg/options"
"github.com/libopenstorage/openstorage/pkg/util"
Expand All @@ -35,6 +36,8 @@ type driver struct {
sdkUds string
conn *grpc.ClientConn
mu sync.Mutex

secretsStore osecrets.Auth
}

type handshakeResp struct {
Expand Down Expand Up @@ -73,11 +76,12 @@ type capabilitiesResponse struct {
Capabilities capabilities
}

func newVolumePlugin(name, sdkUds string) restServer {
func newVolumePlugin(name, sdkUds string, secretsStore osecrets.Auth) restServer {
d := &driver{
restBase: restBase{name: name, version: "0.3"},
SpecHandler: spec.NewSpecHandler(),
sdkUds: sdkUds,
restBase: restBase{name: name, version: "0.3"},
SpecHandler: spec.NewSpecHandler(),
sdkUds: sdkUds,
secretsStore: secretsStore,
}
return d
}
Expand Down Expand Up @@ -217,31 +221,86 @@ func (d *driver) handshake(w http.ResponseWriter, r *http.Request) {
d.logRequest("handshake", "").Debugln("Handshake completed")
}

func (d *driver) attachToken(ctx context.Context, request *volumeRequest) (context.Context, string) {
token, tokenInName := d.GetTokenFromString(request.Name)
if !tokenInName {
token = request.Opts[api.Token]
// attachTokenMount adds the user's token to the context auth metadata
func (d *driver) attachToken(ctx context.Context, request *volumeRequest) (context.Context, string, error) {
token, err := d.parseTokenInput(request.Name, request.Opts)
if err != nil {
return ctx, "", err
}
if len(token) == 0 {
return ctx, ""

return addTokenMetadata(ctx, token), "", nil
}

// attachTokenMount adds the user's token to the context auth metadata
func (d *driver) attachTokenMount(ctx context.Context, request *mountRequest) (context.Context, string, error) {
token, err := d.parseTokenInput(request.Name, make(map[string]string))
if err != nil {
return ctx, "", err
}

md := metadata.New(map[string]string{
"authorization": "bearer " + token,
})
return metadata.NewOutgoingContext(ctx, md), token
return addTokenMetadata(ctx, token), token, nil
}

func (d *driver) attachTokenMount(ctx context.Context, request *mountRequest) (context.Context, string) {
token, _ := d.GetTokenFromString(request.Name)
if len(token) == 0 {
return ctx, ""
// parseTokenInput reads token input from the given name and opts.
// The following is the order of precedence for token in types:
// 1. token=<token> in name
// 2. token in opts
// 3. token_secret=<secret> in name
// 4. token_secret in opts
func (d *driver) parseTokenInput(name string, opts map[string]string) (string, error) {
// get token from name
tokenFromName, tokenInName := d.GetTokenFromString(name)
if tokenInName {
return tokenFromName, nil
}

// get token from opts
tokenFromOpts := opts[api.Token]
if tokenFromOpts != "" {
return tokenFromOpts, nil
}

// get token secret
secret, context, ok := d.GetTokenSecretFromString(name)
if ok {
token, err := d.secretTokenFromStore(secret, context)
if err != nil {
return "", err
}

return token, nil
}

// get token secret from opts
tokenSecretFromOpts := opts[api.TokenSecret]
if tokenSecretFromOpts != "" {
return tokenSecretFromOpts, nil
}

return "", nil
}

// addTokenMetadata adds the token to a given context's metadata
func addTokenMetadata(ctx context.Context, token string) context.Context {
md := metadata.New(map[string]string{
"authorization": "bearer " + token,
})
return metadata.NewOutgoingContext(ctx, md), token
return metadata.NewOutgoingContext(ctx, md)
}

// secretTokenFromStore pulls the token from the configured secret store for
// a given secret name and context.
func (d *driver) secretTokenFromStore(secret, context string) (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, context)
if err != nil {
return "", err
}

return token, nil
}

func (d *driver) status(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -276,7 +335,11 @@ func (d *driver) create(w http.ResponseWriter, r *http.Request) {
}

// attach token in context metadata
ctx, _ = d.attachToken(ctx, request)
ctx, _, err = d.attachToken(ctx, request)
if err != nil {
d.errorResponse(method, w, err)
return
}

// get spec for volume creation
specParsed, spec, locator, source, name := d.SpecFromString(request.Name)
Expand Down Expand Up @@ -329,7 +392,11 @@ func (d *driver) remove(w http.ResponseWriter, r *http.Request) {
}

// attach token in context metadata
ctx, _ = d.attachToken(ctx, request)
ctx, _, err = d.attachToken(ctx, request)
if err != nil {
d.errorResponse(method, w, err)
return
}

// get name for deletion
_, _, _, _, name := d.SpecFromString(request.Name)
Expand Down Expand Up @@ -388,7 +455,11 @@ func (d *driver) mount(w http.ResponseWriter, r *http.Request) {
attachOptions := d.attachOptionsFromSpec(spec)

// attach token in context metadata
ctx, _ = d.attachTokenMount(ctx, request)
ctx, _, err = d.attachTokenMount(ctx, request)
if err != nil {
d.errorResponse(method, w, err)
return
}

// get grpc connection
conn, err := d.getConn()
Expand Down
14 changes: 13 additions & 1 deletion api/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func StartVolumeMgmtAPI(
err error
)
volMgmtApi := newVolumeAPI(name, sdkUds)

if auth {
unixServer, portServer, err = startServerWithAuth(
name,
Expand Down Expand Up @@ -85,8 +86,19 @@ func StartVolumePluginAPI(
name, sdkUds string,
pluginBase string,
pluginPort uint16,
authProviderType secrets.AuthTokenProviders,
authProvider osecrets.Secrets,
) error {
volPluginApi := newVolumePlugin(name, sdkUds)
var secretsStore secrets.Auth
var err error
if authProvider != nil {
secretsStore, err = secrets.NewAuth(authProviderType, authProvider)
if err != nil {
return err
}
}

volPluginApi := newVolumePlugin(name, sdkUds, secretsStore)
if _, _, err := startServer(
name,
pluginBase,
Expand Down
19 changes: 19 additions & 0 deletions api/spec/spec_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ type SpecHandler interface {
// ("", false)
GetTokenFromString(str string) (string, bool)

// GetTokenSecretFromString parses the token secret and context from the name.
// If the token was parsed, it returns:
// (tokenSecret, tokenSecretContext, true)
// If the token wasn't parsed, it returns:
// ("", "", false)
GetTokenSecretFromString(str string) (string, string, bool)

// SpecFromOpts parses in docker options passed in the the docker run
// command of the form --opt name=value
// source is populated if --opt parent=<volume_id> is specified.
Expand Down Expand Up @@ -87,6 +94,7 @@ type SpecHandler interface {
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_-]+/+)*([0-9A-Za-z_-]+)/([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 @@ -385,6 +393,17 @@ func (d *specHandler) GetTokenFromString(str string) (string, bool) {
return token, ok
}

func (d *specHandler) GetTokenSecretFromString(str string) (string, string, bool) {
submatches := tokenSecretRegex.FindStringSubmatch(str)
if len(submatches) < 4 {
return "", "", false
}
context := submatches[2]
secret := submatches[3]

return secret, context, true
}

func (d *specHandler) SpecOptsFromString(
str string,
) (bool, map[string]string, string) {
Expand Down
41 changes: 41 additions & 0 deletions api/spec/spec_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,44 @@ func TestCopyingLabelsFromSpecToLocator(t *testing.T) {
require.Contains(t, locator.VolumeLabels, "hello")
require.Contains(t, locator.VolumeLabels, "goodbye")
}

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

token := "abcd.xyz.123"

tokenParsed, ok := s.GetTokenFromString(fmt.Sprintf("token=%s", token))
require.Equal(t, token, tokenParsed)
require.Equal(t, ok, true)

tokenParsed, ok = s.GetTokenFromString(fmt.Sprintf("toabcbn=%s", token))
require.Equal(t, "", tokenParsed)
require.Equal(t, ok, false)

}

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

secret := "token1"
context := "team1"

sample := "name=abcd,token_secret=/px/secrets/alpha/" + context + "/" + secret + ",token="
sample2 := "name=abcd,token_secret=padsasdax/secr//e/sdasda//ds/asda///sts/al//pha/" + context + "/" + secret + ",token="

secretParsed, contextParsed, ok := s.GetTokenSecretFromString(sample)
require.Equal(t, secret, secretParsed)
require.Equal(t, context, contextParsed)
require.Equal(t, ok, true)

secretParsed, contextParsed, ok = s.GetTokenSecretFromString(sample2)
require.Equal(t, secret, secretParsed)
require.Equal(t, context, contextParsed)
require.Equal(t, ok, true)

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

}
2 changes: 2 additions & 0 deletions cmd/osd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,8 @@ func start(c *cli.Context) error {
d, sdksocket,
volume.PluginAPIBase,
uint16(pluginPort),
0,
nil,
); err != nil {
return fmt.Errorf("Unable to start plugin api server: %v", err)
}
Expand Down

0 comments on commit f349860

Please sign in to comment.