initial commit
This commit is contained in:
4 files changed
+846
No files matched your search
@@ -0,0 +1,227 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
"github.com/hashicorp/go-secure-stdlib/awsutil"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
// AwsPlugin implements the HostPluginServiceServer interface for the
|
||||
// AWS plugin.
|
||||
type AwsPlugin struct {
|
||||
pb.UnimplementedHostPluginServiceServer
|
||||
}
|
||||
|
||||
// Ensure that we are implementing HostPluginServiceServer
|
||||
var (
|
||||
_ pb.HostPluginServiceServer = (*AwsPlugin)(nil)
|
||||
)
|
||||
|
||||
func (p *AwsPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalogRequest) (*pb.OnCreateCatalogResponse, error) {
|
||||
catalog := req.GetCatalog()
|
||||
if catalog == nil {
|
||||
return nil, errors.New("catalog is nil")
|
||||
}
|
||||
secrets := catalog.GetSecrets()
|
||||
if secrets == nil {
|
||||
return nil, errors.New("secrets are required")
|
||||
}
|
||||
attrs := catalog.GetAttributes()
|
||||
if secrets == nil {
|
||||
return nil, errors.New("attributes are required")
|
||||
}
|
||||
|
||||
// TODO: do something to validate that the region is valid on
|
||||
// create
|
||||
_, err := getStringValue(attrs, constRegion, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accessKeyId, err := getStringValue(secrets, constAccessKeyId, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secretAccessKey, err := getStringValue(secrets, constSecretAccessKey, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
skipRotate, err := getBoolValue(secrets, constDisableCredentialRotation, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set up the creds in our persisted state.
|
||||
state := &awsCatalogPersistedState{
|
||||
AccessKeyId: accessKeyId,
|
||||
SecretAccessKey: secretAccessKey,
|
||||
}
|
||||
|
||||
// Try to rotate the credentials if we're not skipping them.
|
||||
if !skipRotate {
|
||||
if err := state.RotateCreds(); err != nil {
|
||||
return nil, fmt.Errorf("error during credential rotation: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &pb.OnCreateCatalogResponse{
|
||||
Persisted: state.ToProto(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *AwsPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalogRequest) (*pb.OnUpdateCatalogResponse, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (p *AwsPlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalogRequest) (*pb.OnDeleteCatalogResponse, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (p *AwsPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest) (*pb.OnCreateSetResponse, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (p *AwsPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest) (*pb.OnUpdateSetResponse, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (p *AwsPlugin) OnDeleteSet(ctx context.Context, req *pb.OnDeleteSetRequest) (*pb.OnDeleteSetResponse, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (p *AwsPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*pb.ListHostsResponse, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
type awsCatalogPersistedState struct {
|
||||
AccessKeyId string
|
||||
SecretAccessKey string
|
||||
CredsLastRotatedTime time.Time
|
||||
}
|
||||
|
||||
func awsCatalogPersistedStateFromProto(in *pb.HostCatalogPersisted) (*awsCatalogPersistedState, error) {
|
||||
data := in.GetData()
|
||||
if data != nil {
|
||||
return nil, errors.New("missing data")
|
||||
}
|
||||
|
||||
accessKeyId, err := getStringValue(data, constAccessKeyId, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("persisted state integrity error: %w", err)
|
||||
}
|
||||
|
||||
secretAccessKey, err := getStringValue(data, constSecretAccessKey, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("persisted state integrity error: %w", err)
|
||||
}
|
||||
|
||||
credsLastRotatedTime, err := getTimeValue(data, constDisableCredentialRotation)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("persisted state integrity error: %w", err)
|
||||
}
|
||||
|
||||
return &awsCatalogPersistedState{
|
||||
AccessKeyId: accessKeyId,
|
||||
SecretAccessKey: secretAccessKey,
|
||||
CredsLastRotatedTime: credsLastRotatedTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *awsCatalogPersistedState) ToProto() (*pb.HostCatalogPersisted, error) {
|
||||
data, err := structpb.NewStruct(map[string]interface{}{
|
||||
constAccessKeyId: s.AccessKeyId,
|
||||
constSecretAccessKey: s.SecretAccessKey,
|
||||
constCredsLastRotatedTime: s.CredsLastRotatedTime.Format(time.RFC3339Nano),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error converting state to structpb.Struct: %w", err)
|
||||
}
|
||||
|
||||
return &pb.HostCatalogPersisted{Data: data}, nil
|
||||
}
|
||||
|
||||
func (s *awsCatalogPersistedState) RotateCreds() error {
|
||||
c, err := awsutil.NewCredentialsConfig(
|
||||
awsutil.WithAccessKey(s.AccessKeyId),
|
||||
awsutil.WithSecretKey(s.SecretAccessKey),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading credentials: %w", err)
|
||||
}
|
||||
|
||||
if err := c.RotateKeys(); err != nil {
|
||||
return fmt.Errorf("error rotating credentials: %w", err)
|
||||
}
|
||||
|
||||
s.AccessKeyId = c.AccessKey
|
||||
s.SecretAccessKey = c.SecretKey
|
||||
s.CredsLastRotatedTime = time.Now()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getStringValue(in *structpb.Struct, k string, required bool) (string, error) {
|
||||
mv := in.AsMap()
|
||||
v, ok := mv[k]
|
||||
if !ok {
|
||||
if required {
|
||||
return "", fmt.Errorf("missing required value %q", k)
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
s, ok := v.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unexpected type for value %q: want string, got %T", k, v)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func getBoolValue(in *structpb.Struct, k string, required bool) (bool, error) {
|
||||
mv := in.AsMap()
|
||||
v, ok := mv[k]
|
||||
if !ok {
|
||||
if required {
|
||||
return false, fmt.Errorf("missing required value %q", k)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
b, ok := v.(bool)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("unexpected type for value %q: want bool, got %T", k, v)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func getTimeValue(in *structpb.Struct, k string) (time.Time, error) {
|
||||
mv := in.AsMap()
|
||||
v, ok := mv[k]
|
||||
if !ok {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
|
||||
tRaw, ok := v.(string)
|
||||
if !ok {
|
||||
return time.Time{}, fmt.Errorf("unexpected type for value %q: want string, got %T", k, v)
|
||||
}
|
||||
|
||||
t, err := time.Parse(time.RFC3339Nano, tRaw)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("could not parse time in value %q: %w", k, err)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
Reference in new issue
Block a user