264 lines
8.9 KiB
Go
264 lines
8.9 KiB
Go
package controlapi
|
|
|
|
import (
|
|
"context"
|
|
"crypto/subtle"
|
|
"strings"
|
|
|
|
"github.com/docker/swarmkit/api"
|
|
"github.com/docker/swarmkit/api/validation"
|
|
"github.com/docker/swarmkit/identity"
|
|
"github.com/docker/swarmkit/log"
|
|
"github.com/docker/swarmkit/manager/state/store"
|
|
"github.com/sirupsen/logrus"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
// 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, status.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, status.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, status.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 status.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 status.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
|
|
}
|
|
|
|
if request.Spec.Driver != nil { // Check that the requested driver is valid
|
|
if _, err := s.dr.NewSecretDriver(request.Spec.Driver); 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, status.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, status.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 status.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 status.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 status.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, status.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 status.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
|
|
}
|
|
if err := validateConfigOrSecretAnnotations(spec.Annotations); err != nil {
|
|
return err
|
|
}
|
|
// Check if secret driver is defined
|
|
if spec.Driver != nil {
|
|
// Ensure secret driver has a name
|
|
if spec.Driver.Name == "" {
|
|
return status.Errorf(codes.InvalidArgument, "secret driver must have a name")
|
|
}
|
|
return nil
|
|
}
|
|
if err := validation.ValidateSecretPayload(spec.Data); err != nil {
|
|
return status.Errorf(codes.InvalidArgument, "%s", err.Error())
|
|
}
|
|
return nil
|
|
}
|