package controlapi import ( "crypto/subtle" "strings" "github.com/Sirupsen/logrus" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/identity" "github.com/docker/swarmkit/log" "github.com/docker/swarmkit/manager/state/store" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) // MaxSecretSize is the maximum byte length of the `Secret.Spec.Data` field. const MaxSecretSize = 500 * 1024 // 500KB // assumes spec is not nil func secretFromSecretSpec(spec *api.SecretSpec) *api.Secret { return &api.Secret{ ID: identity.NewID(), Spec: *spec, } } // GetSecret returns a `GetSecretResponse` with a `Secret` with the same // id as `GetSecretRequest.SecretID` // - Returns `NotFound` if the Secret with the given id is not found. // - Returns `InvalidArgument` if the `GetSecretRequest.SecretID` is empty. // - Returns an error if getting fails. func (s *Server) GetSecret(ctx context.Context, request *api.GetSecretRequest) (*api.GetSecretResponse, error) { if request.SecretID == "" { return nil, grpc.Errorf(codes.InvalidArgument, "secret ID must be provided") } var secret *api.Secret s.store.View(func(tx store.ReadTx) { secret = store.GetSecret(tx, request.SecretID) }) if secret == nil { return nil, grpc.Errorf(codes.NotFound, "secret %s not found", request.SecretID) } secret.Spec.Data = nil // clean the actual secret data so it's never returned return &api.GetSecretResponse{Secret: secret}, nil } // UpdateSecret updates a Secret referenced by SecretID with the given SecretSpec. // - Returns `NotFound` if the Secret is not found. // - Returns `InvalidArgument` if the SecretSpec is malformed or anything other than Labels is changed // - Returns an error if the update fails. func (s *Server) UpdateSecret(ctx context.Context, request *api.UpdateSecretRequest) (*api.UpdateSecretResponse, error) { if request.SecretID == "" || request.SecretVersion == nil { return nil, grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error()) } var secret *api.Secret err := s.store.Update(func(tx store.Tx) error { secret = store.GetSecret(tx, request.SecretID) if secret == nil { return grpc.Errorf(codes.NotFound, "secret %s not found", request.SecretID) } // Check if the Name is different than the current name, or the secret is non-nil and different // than the current secret if secret.Spec.Annotations.Name != request.Spec.Annotations.Name || (request.Spec.Data != nil && subtle.ConstantTimeCompare(request.Spec.Data, secret.Spec.Data) == 0) { return grpc.Errorf(codes.InvalidArgument, "only updates to Labels are allowed") } // We only allow updating Labels secret.Meta.Version = *request.SecretVersion secret.Spec.Annotations.Labels = request.Spec.Annotations.Labels return store.UpdateSecret(tx, secret) }) if err != nil { return nil, err } log.G(ctx).WithFields(logrus.Fields{ "secret.ID": request.SecretID, "secret.Name": request.Spec.Annotations.Name, "method": "UpdateSecret", }).Debugf("secret updated") // WARN: we should never return the actual secret data here. We need to redact the private fields first. secret.Spec.Data = nil return &api.UpdateSecretResponse{ Secret: secret, }, nil } // ListSecrets returns a `ListSecretResponse` with a list all non-internal `Secret`s being // managed, or all secrets matching any name in `ListSecretsRequest.Names`, any // name prefix in `ListSecretsRequest.NamePrefixes`, any id in // `ListSecretsRequest.SecretIDs`, or any id prefix in `ListSecretsRequest.IDPrefixes`. // - Returns an error if listing fails. func (s *Server) ListSecrets(ctx context.Context, request *api.ListSecretsRequest) (*api.ListSecretsResponse, error) { var ( secrets []*api.Secret respSecrets []*api.Secret err error byFilters []store.By by store.By labels map[string]string ) // return all secrets that match either any of the names or any of the name prefixes (why would you give both?) if request.Filters != nil { for _, name := range request.Filters.Names { byFilters = append(byFilters, store.ByName(name)) } for _, prefix := range request.Filters.NamePrefixes { byFilters = append(byFilters, store.ByNamePrefix(prefix)) } for _, prefix := range request.Filters.IDPrefixes { byFilters = append(byFilters, store.ByIDPrefix(prefix)) } labels = request.Filters.Labels } switch len(byFilters) { case 0: by = store.All case 1: by = byFilters[0] default: by = store.Or(byFilters...) } s.store.View(func(tx store.ReadTx) { secrets, err = store.FindSecrets(tx, by) }) if err != nil { return nil, err } // strip secret data from the secret, filter by label, and filter out all internal secrets for _, secret := range secrets { if secret.Internal || !filterMatchLabels(secret.Spec.Annotations.Labels, labels) { continue } secret.Spec.Data = nil // clean the actual secret data so it's never returned respSecrets = append(respSecrets, secret) } return &api.ListSecretsResponse{Secrets: respSecrets}, nil } // CreateSecret creates and returns a `CreateSecretResponse` with a `Secret` based // on the provided `CreateSecretRequest.SecretSpec`. // - Returns `InvalidArgument` if the `CreateSecretRequest.SecretSpec` is malformed, // or if the secret data is too long or contains invalid characters. // - Returns an error if the creation fails. func (s *Server) CreateSecret(ctx context.Context, request *api.CreateSecretRequest) (*api.CreateSecretResponse, error) { if err := validateSecretSpec(request.Spec); err != nil { return nil, err } secret := secretFromSecretSpec(request.Spec) // the store will handle name conflicts err := s.store.Update(func(tx store.Tx) error { return store.CreateSecret(tx, secret) }) switch err { case store.ErrNameConflict: return nil, grpc.Errorf(codes.AlreadyExists, "secret %s already exists", request.Spec.Annotations.Name) case nil: secret.Spec.Data = nil // clean the actual secret data so it's never returned log.G(ctx).WithFields(logrus.Fields{ "secret.Name": request.Spec.Annotations.Name, "method": "CreateSecret", }).Debugf("secret created") return &api.CreateSecretResponse{Secret: secret}, nil default: return nil, err } } // RemoveSecret removes the secret referenced by `RemoveSecretRequest.ID`. // - Returns `InvalidArgument` if `RemoveSecretRequest.ID` is empty. // - Returns `NotFound` if the a secret named `RemoveSecretRequest.ID` is not found. // - Returns `SecretInUse` if the secret is currently in use // - Returns an error if the deletion fails. func (s *Server) RemoveSecret(ctx context.Context, request *api.RemoveSecretRequest) (*api.RemoveSecretResponse, error) { if request.SecretID == "" { return nil, grpc.Errorf(codes.InvalidArgument, "secret ID must be provided") } err := s.store.Update(func(tx store.Tx) error { // Check if the secret exists secret := store.GetSecret(tx, request.SecretID) if secret == nil { return grpc.Errorf(codes.NotFound, "could not find secret %s", request.SecretID) } // Check if any services currently reference this secret, return error if so services, err := store.FindServices(tx, store.ByReferencedSecretID(request.SecretID)) if err != nil { return grpc.Errorf(codes.Internal, "could not find services using secret %s: %v", request.SecretID, err) } if len(services) != 0 { serviceNames := make([]string, 0, len(services)) for _, service := range services { serviceNames = append(serviceNames, service.Spec.Annotations.Name) } secretName := secret.Spec.Annotations.Name serviceNameStr := strings.Join(serviceNames, ", ") serviceStr := "services" if len(serviceNames) == 1 { serviceStr = "service" } return grpc.Errorf(codes.InvalidArgument, "secret '%s' is in use by the following %s: %v", secretName, serviceStr, serviceNameStr) } return store.DeleteSecret(tx, request.SecretID) }) switch err { case store.ErrNotExist: return nil, grpc.Errorf(codes.NotFound, "secret %s not found", request.SecretID) case nil: log.G(ctx).WithFields(logrus.Fields{ "secret.ID": request.SecretID, "method": "RemoveSecret", }).Debugf("secret removed") return &api.RemoveSecretResponse{}, nil default: return nil, err } } func validateSecretSpec(spec *api.SecretSpec) error { if spec == nil { return grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error()) } if err := validateConfigOrSecretAnnotations(spec.Annotations); err != nil { return err } if len(spec.Data) >= MaxSecretSize || len(spec.Data) < 1 { return grpc.Errorf(codes.InvalidArgument, "secret data must be larger than 0 and less than %d bytes", MaxSecretSize) } return nil }