mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-03 07:40:28 +08:00
Adds `UpsertCertAuthority` to the `teleport.v1.trust.TrustService` and deprecates the HTTP api. The `services.Trust` interface was also updated to include a `context.Context` in both `CreateCertAuthority` and `UpsertCertAuthority. To ensure backwards compatibility the `auth.Client` will first attempt to use the gRPC client and if that fails with a not implemented error it will fallback to using HTTP. Part of #6394
333 lines
9.6 KiB
Go
333 lines
9.6 KiB
Go
// Copyright 2021 Gravitational, Inc
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package local
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/gravitational/trace"
|
|
|
|
"github.com/gravitational/teleport/api/types"
|
|
"github.com/gravitational/teleport/lib/backend"
|
|
"github.com/gravitational/teleport/lib/services"
|
|
)
|
|
|
|
// CA is local implementation of Trust service that
|
|
// is using local backend
|
|
type CA struct {
|
|
backend.Backend
|
|
}
|
|
|
|
// NewCAService returns new instance of CAService
|
|
func NewCAService(b backend.Backend) *CA {
|
|
return &CA{
|
|
Backend: b,
|
|
}
|
|
}
|
|
|
|
// DeleteAllCertAuthorities deletes all certificate authorities of a certain type
|
|
func (s *CA) DeleteAllCertAuthorities(caType types.CertAuthType) error {
|
|
startKey := backend.Key(authoritiesPrefix, string(caType))
|
|
return s.DeleteRange(context.TODO(), startKey, backend.RangeEnd(startKey))
|
|
}
|
|
|
|
// CreateCertAuthority updates or inserts a new certificate authority
|
|
func (s *CA) CreateCertAuthority(ctx context.Context, ca types.CertAuthority) error {
|
|
if err := services.ValidateCertAuthority(ca); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
value, err := services.MarshalCertAuthority(ca)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
item := backend.Item{
|
|
Key: backend.Key(authoritiesPrefix, string(ca.GetType()), ca.GetName()),
|
|
Value: value,
|
|
Expires: ca.Expiry(),
|
|
}
|
|
|
|
_, err = s.Create(ctx, item)
|
|
if err != nil {
|
|
if trace.IsAlreadyExists(err) {
|
|
return trace.AlreadyExists("cluster %q already exists", ca.GetName())
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpsertCertAuthority updates or inserts a new certificate authority
|
|
func (s *CA) UpsertCertAuthority(ctx context.Context, ca types.CertAuthority) error {
|
|
if err := services.ValidateCertAuthority(ca); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// try to skip writes that would have no effect
|
|
if existing, err := s.GetCertAuthority(ctx, types.CertAuthID{
|
|
Type: ca.GetType(),
|
|
DomainName: ca.GetClusterName(),
|
|
}, true); err == nil {
|
|
if services.CertAuthoritiesEquivalent(existing, ca) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
value, err := services.MarshalCertAuthority(ca)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
item := backend.Item{
|
|
Key: backend.Key(authoritiesPrefix, string(ca.GetType()), ca.GetName()),
|
|
Value: value,
|
|
Expires: ca.Expiry(),
|
|
ID: ca.GetResourceID(),
|
|
}
|
|
|
|
_, err = s.Put(ctx, item)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CompareAndSwapCertAuthority updates the cert authority value
|
|
// if the existing value matches expected parameter, returns nil if succeeds,
|
|
// trace.CompareFailed otherwise.
|
|
func (s *CA) CompareAndSwapCertAuthority(new, expected types.CertAuthority) error {
|
|
if err := services.ValidateCertAuthority(new); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
key := backend.Key(authoritiesPrefix, string(new.GetType()), new.GetName())
|
|
|
|
actualItem, err := s.Get(context.TODO(), key)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
actual, err := services.UnmarshalCertAuthority(actualItem.Value)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
if !services.CertAuthoritiesEquivalent(actual, expected) {
|
|
return trace.CompareFailed("cluster %v settings have been updated, try again", new.GetName())
|
|
}
|
|
|
|
newValue, err := services.MarshalCertAuthority(new)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
newItem := backend.Item{
|
|
Key: key,
|
|
Value: newValue,
|
|
Expires: new.Expiry(),
|
|
}
|
|
|
|
_, err = s.CompareAndSwap(context.TODO(), *actualItem, newItem)
|
|
if err != nil {
|
|
if trace.IsCompareFailed(err) {
|
|
return trace.CompareFailed("cluster %v settings have been updated, try again", new.GetName())
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeleteCertAuthority deletes particular certificate authority
|
|
func (s *CA) DeleteCertAuthority(ctx context.Context, id types.CertAuthID) error {
|
|
if err := id.Check(); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
// when removing a types.CertAuthority also remove any deactivated
|
|
// types.CertAuthority as well if they exist.
|
|
err := s.Delete(ctx, backend.Key(authoritiesPrefix, deactivatedPrefix, string(id.Type), id.DomainName))
|
|
if err != nil {
|
|
if !trace.IsNotFound(err) {
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
err = s.Delete(ctx, backend.Key(authoritiesPrefix, string(id.Type), id.DomainName))
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ActivateCertAuthority moves a CertAuthority from the deactivated list to
|
|
// the normal list.
|
|
func (s *CA) ActivateCertAuthority(id types.CertAuthID) error {
|
|
item, err := s.Get(context.TODO(), backend.Key(authoritiesPrefix, deactivatedPrefix, string(id.Type), id.DomainName))
|
|
if err != nil {
|
|
if trace.IsNotFound(err) {
|
|
return trace.BadParameter("can not activate cert authority %q which has not been deactivated", id.DomainName)
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
certAuthority, err := services.UnmarshalCertAuthority(
|
|
item.Value, services.WithResourceID(item.ID), services.WithExpires(item.Expires))
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
err = s.UpsertCertAuthority(context.TODO(), certAuthority)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
err = s.Delete(context.TODO(), backend.Key(authoritiesPrefix, deactivatedPrefix, string(id.Type), id.DomainName))
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeactivateCertAuthority moves a CertAuthority from the normal list to
|
|
// the deactivated list.
|
|
func (s *CA) DeactivateCertAuthority(id types.CertAuthID) error {
|
|
certAuthority, err := s.GetCertAuthority(context.TODO(), id, true)
|
|
if err != nil {
|
|
if trace.IsNotFound(err) {
|
|
return trace.NotFound("can not deactivate cert authority %q which does not exist", id.DomainName)
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
err = s.DeleteCertAuthority(context.TODO(), id)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
value, err := services.MarshalCertAuthority(certAuthority)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
item := backend.Item{
|
|
Key: backend.Key(authoritiesPrefix, deactivatedPrefix, string(id.Type), id.DomainName),
|
|
Value: value,
|
|
Expires: certAuthority.Expiry(),
|
|
ID: certAuthority.GetResourceID(),
|
|
}
|
|
|
|
_, err = s.Put(context.TODO(), item)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetCertAuthority returns certificate authority by given id. Parameter loadSigningKeys
|
|
// controls if signing keys are loaded
|
|
func (s *CA) GetCertAuthority(ctx context.Context, id types.CertAuthID, loadSigningKeys bool) (types.CertAuthority, error) {
|
|
if err := id.Check(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
item, err := s.Get(ctx, backend.Key(authoritiesPrefix, string(id.Type), id.DomainName))
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
ca, err := services.UnmarshalCertAuthority(item.Value, services.WithResourceID(item.ID), services.WithExpires(item.Expires))
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := services.ValidateCertAuthority(ca); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
setSigningKeys(ca, loadSigningKeys)
|
|
return ca, nil
|
|
}
|
|
|
|
func setSigningKeys(ca types.CertAuthority, loadSigningKeys bool) {
|
|
if loadSigningKeys {
|
|
return
|
|
}
|
|
types.RemoveCASecrets(ca)
|
|
}
|
|
|
|
// GetCertAuthorities returns a list of authorities of a given type
|
|
// loadSigningKeys controls whether signing keys should be loaded or not
|
|
func (s *CA) GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadSigningKeys bool) ([]types.CertAuthority, error) {
|
|
if err := caType.Check(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// Get all items in the bucket.
|
|
startKey := backend.Key(authoritiesPrefix, string(caType))
|
|
result, err := s.GetRange(ctx, startKey, backend.RangeEnd(startKey), backend.NoLimit)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// Marshal values into a []types.CertAuthority slice.
|
|
cas := make([]types.CertAuthority, len(result.Items))
|
|
for i, item := range result.Items {
|
|
ca, err := services.UnmarshalCertAuthority(item.Value, services.WithResourceID(item.ID), services.WithExpires(item.Expires))
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := services.ValidateCertAuthority(ca); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
setSigningKeys(ca, loadSigningKeys)
|
|
cas[i] = ca
|
|
}
|
|
|
|
return cas, nil
|
|
}
|
|
|
|
// UpdateUserCARoleMap updates the role map of the userCA of the specified existing cluster.
|
|
func (s *CA) UpdateUserCARoleMap(ctx context.Context, name string, roleMap types.RoleMap, activated bool) error {
|
|
key := backend.Key(authoritiesPrefix, string(types.UserCA), name)
|
|
if !activated {
|
|
key = backend.Key(authoritiesPrefix, deactivatedPrefix, string(types.UserCA), name)
|
|
}
|
|
|
|
actualItem, err := s.Get(ctx, key)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
actual, err := services.UnmarshalCertAuthority(actualItem.Value)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
actual.SetRoleMap(roleMap)
|
|
|
|
newValue, err := services.MarshalCertAuthority(actual)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
newItem := backend.Item{
|
|
Key: key,
|
|
Value: newValue,
|
|
Expires: actual.Expiry(),
|
|
}
|
|
_, err = s.CompareAndSwap(ctx, *actualItem, newItem)
|
|
if err != nil {
|
|
if trace.IsCompareFailed(err) {
|
|
return trace.CompareFailed("cluster %v settings have been updated, try again", name)
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
const (
|
|
authoritiesPrefix = "authorities"
|
|
deactivatedPrefix = "deactivated"
|
|
)
|