mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-02 23:31:08 +08:00
272 lines
8.5 KiB
Go
272 lines
8.5 KiB
Go
/*
|
|
* Teleport
|
|
* Copyright (C) 2023 Gravitational, Inc.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
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)
|
|
}
|
|
|
|
// 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(ctx context.Context, role types.SystemRole) (*StateV2, error) {
|
|
item, err := p.stateStorage.Get(ctx, 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"`
|
|
}
|