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

Add support to pass security token through token_secret in DC/OS #943

Merged
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
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