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

systemview: adds method for plugins to generate identity tokens #24929

Merged
merged 4 commits into from
Jan 18, 2024
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
3 changes: 3 additions & 0 deletions changelog/24929.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
sdk: adds new method to system view to allow plugins to request identity tokens
```
40 changes: 40 additions & 0 deletions sdk/helper/pluginutil/identity_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package pluginutil

import (
"time"
)

const redactedTokenString = "ey***"

type IdentityTokenRequest struct {
// Audience identifies the recipient of the token. The requested
// value will be in the "aud" claim. Required.
Audience string
// TTL is the requested duration that the token will be valid for.
// Optional with a default of 1hr.
TTL time.Duration
}

type IdentityTokenResponse struct {
// Token is the plugin identity token.
Token IdentityToken
// TTL is the duration that the token is valid for after truncation is applied.
// The TTL may be truncated depending on the lifecycle of its signing key.
TTL time.Duration
}

type IdentityToken string

// String returns a redacted token string. Use the Token() method
// to obtain the non-redacted token contents.
func (t IdentityToken) String() string {
return redactedTokenString
}

// Token returns the non-redacted token contents.
func (t IdentityToken) Token() string {
return string(t)
}
29 changes: 29 additions & 0 deletions sdk/helper/pluginutil/identity_token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package pluginutil

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

// TestIdentityToken_Stringer ensures that plugin identity tokens that
// are printed in formatted strings or errors are redacted and getters
// return expected values.
func TestIdentityToken_Stringer(t *testing.T) {
contents := "header.payload.signature"
tk := IdentityToken(contents)

// token getters
assert.Equal(t, contents, tk.Token())
assert.Equal(t, redactedTokenString, tk.String())

// formatted strings and errors
assert.NotContains(t, fmt.Sprintf("%v", tk), tk.Token())
assert.NotContains(t, fmt.Sprintf("%s", tk), tk.Token())
assert.NotContains(t, fmt.Errorf("%v", tk).Error(), tk.Token())
assert.NotContains(t, fmt.Errorf("%s", tk).Error(), tk.Token())
}
7 changes: 7 additions & 0 deletions sdk/logical/system_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ type SystemView interface {
// write forwarding (WriteForwardedPaths). This value will be templated
// in for the {{cluterId}} sentinel.
ClusterID(ctx context.Context) (string, error)

// GenerateIdentityToken returns an identity token for the requesting plugin.
GenerateIdentityToken(ctx context.Context, req *pluginutil.IdentityTokenRequest) (*pluginutil.IdentityTokenResponse, error)
}

type PasswordPolicy interface {
Expand Down Expand Up @@ -265,6 +268,10 @@ func (d StaticSystemView) ClusterID(ctx context.Context) (string, error) {
return d.ClusterUUID, nil
}

func (d StaticSystemView) GenerateIdentityToken(_ context.Context, _ *pluginutil.IdentityTokenRequest) (*pluginutil.IdentityTokenResponse, error) {
return nil, errors.New("GenerateIdentityToken is not implemented in StaticSystemView")
}

func (d StaticSystemView) APILockShouldBlockRequest() (bool, error) {
return d.APILockShouldBlockRequestVal, nil
}
35 changes: 35 additions & 0 deletions sdk/plugin/grpc_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,21 @@ func (s gRPCSystemViewClient) ClusterID(ctx context.Context) (string, error) {
return reply.ClusterID, nil
}

func (s *gRPCSystemViewClient) GenerateIdentityToken(ctx context.Context, req *pluginutil.IdentityTokenRequest) (*pluginutil.IdentityTokenResponse, error) {
resp, err := s.client.GenerateIdentityToken(ctx, &pb.GenerateIdentityTokenRequest{
Audience: req.Audience,
TTL: int64(req.TTL.Seconds()),
})
if err != nil {
return nil, err
}

return &pluginutil.IdentityTokenResponse{
Token: pluginutil.IdentityToken(resp.Token),
TTL: time.Duration(resp.TTL) * time.Second,
}, nil
}

type gRPCSystemViewServer struct {
pb.UnimplementedSystemViewServer

Expand Down Expand Up @@ -394,3 +409,23 @@ func (s *gRPCSystemViewServer) ClusterInfo(ctx context.Context, _ *pb.Empty) (*p
ClusterID: clusterId,
}, nil
}

func (s *gRPCSystemViewServer) GenerateIdentityToken(ctx context.Context, req *pb.GenerateIdentityTokenRequest) (*pb.GenerateIdentityTokenResponse, error) {
if s.impl == nil {
return nil, errMissingSystemView
}

res, err := s.impl.GenerateIdentityToken(ctx, &pluginutil.IdentityTokenRequest{
Audience: req.GetAudience(),
TTL: time.Duration(req.GetTTL()) * time.Second,
})
if err != nil {
return &pb.GenerateIdentityTokenResponse{}, status.Errorf(codes.Internal,
"failed to generate plugin identity token")
}

return &pb.GenerateIdentityTokenResponse{
Token: res.Token.Token(),
TTL: int64(res.TTL.Seconds()),
}, nil
}
Loading
Loading