7 "github.com/Sirupsen/logrus"
8 "github.com/docker/swarmkit/api"
9 "github.com/docker/swarmkit/identity"
10 "github.com/docker/swarmkit/log"
11 "github.com/docker/swarmkit/manager/state/store"
12 "golang.org/x/net/context"
13 "google.golang.org/grpc"
14 "google.golang.org/grpc/codes"
17 // MaxSecretSize is the maximum byte length of the `Secret.Spec.Data` field.
18 const MaxSecretSize = 500 * 1024 // 500KB
20 // assumes spec is not nil
21 func secretFromSecretSpec(spec *api.SecretSpec) *api.Secret {
28 // GetSecret returns a `GetSecretResponse` with a `Secret` with the same
29 // id as `GetSecretRequest.SecretID`
30 // - Returns `NotFound` if the Secret with the given id is not found.
31 // - Returns `InvalidArgument` if the `GetSecretRequest.SecretID` is empty.
32 // - Returns an error if getting fails.
33 func (s *Server) GetSecret(ctx context.Context, request *api.GetSecretRequest) (*api.GetSecretResponse, error) {
34 if request.SecretID == "" {
35 return nil, grpc.Errorf(codes.InvalidArgument, "secret ID must be provided")
38 var secret *api.Secret
39 s.store.View(func(tx store.ReadTx) {
40 secret = store.GetSecret(tx, request.SecretID)
44 return nil, grpc.Errorf(codes.NotFound, "secret %s not found", request.SecretID)
47 secret.Spec.Data = nil // clean the actual secret data so it's never returned
48 return &api.GetSecretResponse{Secret: secret}, nil
51 // UpdateSecret updates a Secret referenced by SecretID with the given SecretSpec.
52 // - Returns `NotFound` if the Secret is not found.
53 // - Returns `InvalidArgument` if the SecretSpec is malformed or anything other than Labels is changed
54 // - Returns an error if the update fails.
55 func (s *Server) UpdateSecret(ctx context.Context, request *api.UpdateSecretRequest) (*api.UpdateSecretResponse, error) {
56 if request.SecretID == "" || request.SecretVersion == nil {
57 return nil, grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
60 var secret *api.Secret
61 err := s.store.Update(func(tx store.Tx) error {
62 secret = store.GetSecret(tx, request.SecretID)
64 return grpc.Errorf(codes.NotFound, "secret %s not found", request.SecretID)
67 // Check if the Name is different than the current name, or the secret is non-nil and different
68 // than the current secret
69 if secret.Spec.Annotations.Name != request.Spec.Annotations.Name ||
70 (request.Spec.Data != nil && subtle.ConstantTimeCompare(request.Spec.Data, secret.Spec.Data) == 0) {
71 return grpc.Errorf(codes.InvalidArgument, "only updates to Labels are allowed")
74 // We only allow updating Labels
75 secret.Meta.Version = *request.SecretVersion
76 secret.Spec.Annotations.Labels = request.Spec.Annotations.Labels
78 return store.UpdateSecret(tx, secret)
84 log.G(ctx).WithFields(logrus.Fields{
85 "secret.ID": request.SecretID,
86 "secret.Name": request.Spec.Annotations.Name,
87 "method": "UpdateSecret",
88 }).Debugf("secret updated")
90 // WARN: we should never return the actual secret data here. We need to redact the private fields first.
91 secret.Spec.Data = nil
92 return &api.UpdateSecretResponse{
97 // ListSecrets returns a `ListSecretResponse` with a list all non-internal `Secret`s being
98 // managed, or all secrets matching any name in `ListSecretsRequest.Names`, any
99 // name prefix in `ListSecretsRequest.NamePrefixes`, any id in
100 // `ListSecretsRequest.SecretIDs`, or any id prefix in `ListSecretsRequest.IDPrefixes`.
101 // - Returns an error if listing fails.
102 func (s *Server) ListSecrets(ctx context.Context, request *api.ListSecretsRequest) (*api.ListSecretsResponse, error) {
104 secrets []*api.Secret
105 respSecrets []*api.Secret
109 labels map[string]string
112 // return all secrets that match either any of the names or any of the name prefixes (why would you give both?)
113 if request.Filters != nil {
114 for _, name := range request.Filters.Names {
115 byFilters = append(byFilters, store.ByName(name))
117 for _, prefix := range request.Filters.NamePrefixes {
118 byFilters = append(byFilters, store.ByNamePrefix(prefix))
120 for _, prefix := range request.Filters.IDPrefixes {
121 byFilters = append(byFilters, store.ByIDPrefix(prefix))
123 labels = request.Filters.Labels
126 switch len(byFilters) {
132 by = store.Or(byFilters...)
135 s.store.View(func(tx store.ReadTx) {
136 secrets, err = store.FindSecrets(tx, by)
142 // strip secret data from the secret, filter by label, and filter out all internal secrets
143 for _, secret := range secrets {
144 if secret.Internal || !filterMatchLabels(secret.Spec.Annotations.Labels, labels) {
147 secret.Spec.Data = nil // clean the actual secret data so it's never returned
148 respSecrets = append(respSecrets, secret)
151 return &api.ListSecretsResponse{Secrets: respSecrets}, nil
154 // CreateSecret creates and returns a `CreateSecretResponse` with a `Secret` based
155 // on the provided `CreateSecretRequest.SecretSpec`.
156 // - Returns `InvalidArgument` if the `CreateSecretRequest.SecretSpec` is malformed,
157 // or if the secret data is too long or contains invalid characters.
158 // - Returns an error if the creation fails.
159 func (s *Server) CreateSecret(ctx context.Context, request *api.CreateSecretRequest) (*api.CreateSecretResponse, error) {
160 if err := validateSecretSpec(request.Spec); err != nil {
164 secret := secretFromSecretSpec(request.Spec) // the store will handle name conflicts
165 err := s.store.Update(func(tx store.Tx) error {
166 return store.CreateSecret(tx, secret)
170 case store.ErrNameConflict:
171 return nil, grpc.Errorf(codes.AlreadyExists, "secret %s already exists", request.Spec.Annotations.Name)
173 secret.Spec.Data = nil // clean the actual secret data so it's never returned
174 log.G(ctx).WithFields(logrus.Fields{
175 "secret.Name": request.Spec.Annotations.Name,
176 "method": "CreateSecret",
177 }).Debugf("secret created")
179 return &api.CreateSecretResponse{Secret: secret}, nil
185 // RemoveSecret removes the secret referenced by `RemoveSecretRequest.ID`.
186 // - Returns `InvalidArgument` if `RemoveSecretRequest.ID` is empty.
187 // - Returns `NotFound` if the a secret named `RemoveSecretRequest.ID` is not found.
188 // - Returns `SecretInUse` if the secret is currently in use
189 // - Returns an error if the deletion fails.
190 func (s *Server) RemoveSecret(ctx context.Context, request *api.RemoveSecretRequest) (*api.RemoveSecretResponse, error) {
191 if request.SecretID == "" {
192 return nil, grpc.Errorf(codes.InvalidArgument, "secret ID must be provided")
195 err := s.store.Update(func(tx store.Tx) error {
196 // Check if the secret exists
197 secret := store.GetSecret(tx, request.SecretID)
199 return grpc.Errorf(codes.NotFound, "could not find secret %s", request.SecretID)
202 // Check if any services currently reference this secret, return error if so
203 services, err := store.FindServices(tx, store.ByReferencedSecretID(request.SecretID))
205 return grpc.Errorf(codes.Internal, "could not find services using secret %s: %v", request.SecretID, err)
208 if len(services) != 0 {
209 serviceNames := make([]string, 0, len(services))
210 for _, service := range services {
211 serviceNames = append(serviceNames, service.Spec.Annotations.Name)
214 secretName := secret.Spec.Annotations.Name
215 serviceNameStr := strings.Join(serviceNames, ", ")
216 serviceStr := "services"
217 if len(serviceNames) == 1 {
218 serviceStr = "service"
221 return grpc.Errorf(codes.InvalidArgument, "secret '%s' is in use by the following %s: %v", secretName, serviceStr, serviceNameStr)
224 return store.DeleteSecret(tx, request.SecretID)
227 case store.ErrNotExist:
228 return nil, grpc.Errorf(codes.NotFound, "secret %s not found", request.SecretID)
230 log.G(ctx).WithFields(logrus.Fields{
231 "secret.ID": request.SecretID,
232 "method": "RemoveSecret",
233 }).Debugf("secret removed")
235 return &api.RemoveSecretResponse{}, nil
241 func validateSecretSpec(spec *api.SecretSpec) error {
243 return grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
245 if err := validateConfigOrSecretAnnotations(spec.Annotations); err != nil {
249 if len(spec.Data) >= MaxSecretSize || len(spec.Data) < 1 {
250 return grpc.Errorf(codes.InvalidArgument, "secret data must be larger than 0 and less than %d bytes", MaxSecretSize)