mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-31 20:39:37 +08:00
276 lines
8.7 KiB
Go
276 lines
8.7 KiB
Go
/*
|
|
Copyright 2019 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 auth
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
|
|
"github.com/gravitational/trace"
|
|
|
|
"github.com/gravitational/teleport/api/client/proto"
|
|
"github.com/gravitational/teleport/api/types"
|
|
"github.com/gravitational/teleport/lib/backend"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
)
|
|
|
|
// stateBackend implements abstraction over local or remote storage backend methods
|
|
// required for Identity/State storage.
|
|
// As in backend.Backend, Item keys are assumed to be valid UTF8, which may be enforced by the
|
|
// various Backend implementations.
|
|
type stateBackend interface {
|
|
// Create creates item if it does not exist
|
|
Create(ctx context.Context, i backend.Item) (*backend.Lease, error)
|
|
// Put puts value into backend (creates if it does not
|
|
// exists, updates it otherwise)
|
|
Put(ctx context.Context, i backend.Item) (*backend.Lease, error)
|
|
// Get returns a single item or not found error
|
|
Get(ctx context.Context, key []byte) (*backend.Item, error)
|
|
// PutRange receives multiple items and upserts them into the Kubernetes Secret.
|
|
// This function is only used when the Agent's Secret does not exist, but local SQLite database
|
|
// has identity credentials.
|
|
// TODO(tigrato): remove this once the compatibility layer between local storage and
|
|
// Kube secret storage is no longer required!
|
|
PutRange(ctx context.Context, items []backend.Item) error
|
|
}
|
|
|
|
// ProcessStorage is a backend for local process state,
|
|
// it helps to manage rotation for certificate authorities
|
|
// and keeps local process credentials - x509 and SSH certs and keys.
|
|
type ProcessStorage struct {
|
|
// BackendStorage is the SQLite backend used for operations unrelated to storing/reading identities and states.
|
|
BackendStorage backend.Backend
|
|
|
|
// stateStorage is the backend to store agents' identities and states.
|
|
// it is not required to close stateBackend storage because it's either the same as BackendStorage or it is Kubernetes
|
|
// which does not require any close method
|
|
stateStorage stateBackend
|
|
}
|
|
|
|
// Close closes all resources used by process storage backend.
|
|
func (p *ProcessStorage) Close() error {
|
|
// we do not need to close stateBackend storage because it's either the same as backend or it's kubernetes
|
|
// which does not require any close method
|
|
return p.BackendStorage.Close()
|
|
}
|
|
|
|
const (
|
|
// IdentityCurrent is a name for the identity credentials that are
|
|
// currently used by the process.
|
|
IdentityCurrent = "current"
|
|
// IdentityReplacement is a name for the identity credentials that are
|
|
// replacing current identity credentials during CA rotation.
|
|
IdentityReplacement = "replacement"
|
|
// stateName is an internal resource object name
|
|
stateName = "state"
|
|
// statesPrefix is a key prefix for object states
|
|
statesPrefix = "states"
|
|
// idsPrefix is a key prefix for identities
|
|
idsPrefix = "ids"
|
|
)
|
|
|
|
// GetState reads rotation state from disk.
|
|
func (p *ProcessStorage) GetState(role types.SystemRole) (*StateV2, error) {
|
|
item, err := p.stateStorage.Get(context.TODO(), backend.Key(statesPrefix, strings.ToLower(role.String()), stateName))
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
var res StateV2
|
|
if err := utils.FastUnmarshal(item.Value, &res); err != nil {
|
|
return nil, trace.BadParameter(err.Error())
|
|
}
|
|
if err := res.CheckAndSetDefaults(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &res, nil
|
|
}
|
|
|
|
// CreateState creates process state if it does not exist yet.
|
|
func (p *ProcessStorage) CreateState(role types.SystemRole, state StateV2) error {
|
|
if err := state.CheckAndSetDefaults(); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
value, err := json.Marshal(state)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
item := backend.Item{
|
|
Key: backend.Key(statesPrefix, strings.ToLower(role.String()), stateName),
|
|
Value: value,
|
|
}
|
|
_, err = p.stateStorage.Create(context.TODO(), item)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WriteState writes local cluster state to the backend.
|
|
func (p *ProcessStorage) WriteState(role types.SystemRole, state StateV2) error {
|
|
if err := state.CheckAndSetDefaults(); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
value, err := json.Marshal(state)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
item := backend.Item{
|
|
Key: backend.Key(statesPrefix, strings.ToLower(role.String()), stateName),
|
|
Value: value,
|
|
}
|
|
_, err = p.stateStorage.Put(context.TODO(), item)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReadIdentity reads identity using identity name and role.
|
|
func (p *ProcessStorage) ReadIdentity(name string, role types.SystemRole) (*Identity, error) {
|
|
if name == "" {
|
|
return nil, trace.BadParameter("missing parameter name")
|
|
}
|
|
item, err := p.stateStorage.Get(context.TODO(), backend.Key(idsPrefix, strings.ToLower(role.String()), name))
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
var res IdentityV2
|
|
if err := utils.FastUnmarshal(item.Value, &res); err != nil {
|
|
return nil, trace.BadParameter(err.Error())
|
|
}
|
|
if err := res.CheckAndSetDefaults(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return ReadIdentityFromKeyPair(res.Spec.Key, &proto.Certs{
|
|
SSH: res.Spec.SSHCert,
|
|
TLS: res.Spec.TLSCert,
|
|
TLSCACerts: res.Spec.TLSCACerts,
|
|
SSHCACerts: res.Spec.SSHCACerts,
|
|
})
|
|
}
|
|
|
|
// WriteIdentity writes identity to the backend.
|
|
func (p *ProcessStorage) WriteIdentity(name string, id Identity) error {
|
|
res := IdentityV2{
|
|
ResourceHeader: types.ResourceHeader{
|
|
Kind: types.KindIdentity,
|
|
Version: types.V2,
|
|
Metadata: types.Metadata{
|
|
Name: name,
|
|
},
|
|
},
|
|
Spec: IdentitySpecV2{
|
|
Key: id.KeyBytes,
|
|
SSHCert: id.CertBytes,
|
|
TLSCert: id.TLSCertBytes,
|
|
TLSCACerts: id.TLSCACertsBytes,
|
|
SSHCACerts: id.SSHCACertBytes,
|
|
},
|
|
}
|
|
if err := res.CheckAndSetDefaults(); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
value, err := json.Marshal(res)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
item := backend.Item{
|
|
Key: backend.Key(idsPrefix, strings.ToLower(id.ID.Role.String()), name),
|
|
Value: value,
|
|
}
|
|
_, err = p.stateStorage.Put(context.TODO(), item)
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// StateV2 is a local process state.
|
|
type StateV2 struct {
|
|
// ResourceHeader is a common resource header.
|
|
types.ResourceHeader
|
|
// Spec is a process spec.
|
|
Spec StateSpecV2 `json:"spec"`
|
|
}
|
|
|
|
// CheckAndSetDefaults checks and sets defaults values.
|
|
func (s *StateV2) CheckAndSetDefaults() error {
|
|
s.Kind = types.KindState
|
|
s.Version = types.V2
|
|
// for state resource name does not matter
|
|
if s.Metadata.Name == "" {
|
|
s.Metadata.Name = stateName
|
|
}
|
|
if err := s.Metadata.CheckAndSetDefaults(); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StateSpecV2 is a state spec.
|
|
type StateSpecV2 struct {
|
|
// Rotation holds local process rotation state.
|
|
Rotation types.Rotation `json:"rotation"`
|
|
}
|
|
|
|
// IdentityV2 specifies local host identity.
|
|
type IdentityV2 struct {
|
|
// ResourceHeader is a common resource header.
|
|
types.ResourceHeader
|
|
// Spec is the identity spec.
|
|
Spec IdentitySpecV2 `json:"spec"`
|
|
}
|
|
|
|
// CheckAndSetDefaults checks and sets defaults values.
|
|
func (s *IdentityV2) CheckAndSetDefaults() error {
|
|
s.Kind = types.KindIdentity
|
|
s.Version = types.V2
|
|
if err := s.Metadata.CheckAndSetDefaults(); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
if len(s.Spec.Key) == 0 {
|
|
return trace.BadParameter("missing parameter Key")
|
|
}
|
|
if len(s.Spec.SSHCert) == 0 {
|
|
return trace.BadParameter("missing parameter SSHCert")
|
|
}
|
|
if len(s.Spec.TLSCert) == 0 {
|
|
return trace.BadParameter("missing parameter TLSCert")
|
|
}
|
|
if len(s.Spec.TLSCACerts) == 0 {
|
|
return trace.BadParameter("missing parameter TLSCACerts")
|
|
}
|
|
if len(s.Spec.SSHCACerts) == 0 {
|
|
return trace.BadParameter("missing parameter SSH CA bytes")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// IdentitySpecV2 specifies credentials used by local process.
|
|
type IdentitySpecV2 struct {
|
|
// Key is a PEM encoded private key.
|
|
Key []byte `json:"key,omitempty"`
|
|
// SSHCert is a PEM encoded SSH host cert.
|
|
SSHCert []byte `json:"ssh_cert,omitempty"`
|
|
// TLSCert is a PEM encoded x509 client certificate.
|
|
TLSCert []byte `json:"tls_cert,omitempty"`
|
|
// TLSCACert is a list of PEM encoded x509 certificate of the
|
|
// certificate authority of the cluster.
|
|
TLSCACerts [][]byte `json:"tls_ca_certs,omitempty"`
|
|
// SSHCACerts is a list of SSH certificate authorities encoded in the
|
|
// authorized_keys format.
|
|
SSHCACerts [][]byte `json:"ssh_ca_certs,omitempty"`
|
|
}
|