123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- 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
- }
|