Hashicorp Vault 복구 키 복원


TL;박사 01 명
  • 복구 키 생성
  • 임의로 생성된 DEK 키
  • 암호화 복구 키 사용
  • KMS 서비스로 DEK 키 암호화
  • 암호화 복구 키 + 암호화 DEK 키 + IV+ KMS 키 정보 서열화
  • 바이너리 데이터를 스토리지 백엔드에 영구화

  • 모티프
    지난 몇 주 동안 저는 Vault와 협력해 왔습니다. Google Workspace의 SSO, AWS 인증, 신분 관리, 동적 ACL, 동적 기밀, 자동 봉인을 사용하여 Google Kubernetes 그룹에서 작업을 할 수 있도록 하는 등 Vault 시스템을 생산하는 주요 주제에 대해 토론을 진행했습니다.많은 지역사회와 문서를 가진Vault 사용자 덕분에 테스트 그룹에서 예상대로 실행할 수 있습니다.
    Vault가 자동 해제로 설정되었을 때, Vault를 처음 실행할 때 vault operator init 복구 키와 루트 카드를 생성하고, 이 기밀을 어느 위치에 저장하거나 백업해야 한다는 것을 알 수 있습니다.루트 토큰은 슈퍼 권한을 가진Vault 토큰으로Vault 시스템에서 모든 작업을 수행할 수 있으며 복구 키는 대량의 사용자가 실행해야 하는 기능(예를 들어 루트 토큰을 다시 생성하는 것)에 사용될 것이다.궁금해요.

    복구 키와 루트 토큰을 잃어버리면 Vault에 가장 높은 권한으로 접근하려면 어떻게 해야 합니까?
    앞에서 말한 바와 같이 복구 키는 루트 영패를 다시 생성하는 데 사용할 수 있습니다.따라서 복구 키를 어떤 방식으로 복구할 수 있다면 복구 키를 사용하여 루트 카드를 다시 생성할 수 있습니다.이 블로그에서, 나는Vault 소스 코드를 보면서 키를 복구하는 과정을 이해할 것이다.시작하다
    The life of recovery key begin with
    if recoveryConfig.SecretShares > 0 {
        recoveryKey, recoveryUnsealKeys, err := c.generateShares(recoveryConfig)
        if err != nil {
            c.logger.Error("failed to generate recovery shares", "error", err)
            return nil, err
        }
    
        err = c.seal.SetRecoveryKey(ctx, recoveryKey)
        if err != nil {
            return nil, err
        }
    
        results.RecoveryShares = recoveryUnsealKeys
    }
    
    그것은 generated by *Core.generateShares 이고 *Core.seal.SetRecoveryKey 에 전달되어 암호화되고 장기적으로 저장된다.더 나아가 보면 *Core.seal는 유형Seal의 변수이고 또 하나의 인터페이스이다.For auto-unseal feature, the implementation be like
     func (d *autoSeal) SetRecoveryKey(ctx context.Context, key []byte) error {
        if err := d.checkCore(); err != nil {
          return err
        }
    
        if key == nil {
          return fmt.Errorf("recovery key to store is nil")
        }
    
        // Encrypt and marshal the keys
        blobInfo, err := d.Encrypt(ctx, key, nil)
        if err != nil {
          return errwrap.Wrapf("failed to encrypt keys for storage: {{err}}", err)
        }
    
        value, err := proto.Marshal(blobInfo)
        if err != nil {
          return errwrap.Wrapf("failed to marshal value for storage: {{err}}", err)
        }
    
        be := &physical.Entry{
          Key:   recoveryKeyPath,
          Value: value,
        }
    
        if err := d.core.physical.Put(ctx, be); err != nil {
          d.logger.Error("failed to write recovery key", "error", err)
          return errwrap.Wrapf("failed to write recovery key: {{err}}", err)
        }
    
        return nil
      }
    
    나는 생성 과정에 관한 내용을 너무 많이 이야기하지는 않지만, 다음 과정을 깊이 있게 연구할 것이다.코드 세그먼트에서 주요 기능과 작업을 볼 수 있습니다.
    *autoSeal.Encrypt 암호화 복구 키 사용
    이 함수는 내부 지표를 설정한 다음 wrapping.Wrapper.Encrypt를 호출하여 실제 암호화합니다.패키지wrappinga library support encrypt things through various KMS providers.README.md저희가 노트가 좀 있어요.
    For KMS providers that do not support encrypting arbitrarily large values, the library will generate an envelope data encryption key (DEK), encrypt the value with it using an authenticated cipher, and use the KMS to encrypt the DEK.
    ....
    Supports many KMSes:
      AEAD using AES-GCM and a provided key
      Alibaba Cloud KMS (uses envelopes)
      AWS KMS (uses envelopes)
      Azure KeyVault (uses envelopes)
      GCP CKMS (uses envelopes)
      Huawei Cloud KMS (uses envelopes)
      OCI KMS (uses envelopes)
      Tencent Cloud KMS (uses envelopes)
      Vault Transit mount
      Yandex.Cloud KMS (uses envelopes)
    Transparently supports multiple decryption targets, allowing for key rotation
    Supports Additional Authenticated Data (AAD) for all KMSes except Vault Transit.
    
    잠시 후 우리는 Protocol Buffer 부분에서 봉투에 관한 더 많은 내용을 토론할 것이다.암호화 포장 함수로 돌아갑니다.구현은 다음과 같습니다.
    // https://github.com/hashicorp/go-kms-wrapping/blob/master/wrapper.go#L47
    
    type Wrapper interface {
        // Type is the type of Wrapper
        Type() string
    
        // KeyID is the ID of the key currently used for encryption
        KeyID() string
        // HMACKeyID is the ID of the key currently used for HMACing (if any)
        HMACKeyID() string
    
        // Init allows performing any necessary setup calls before using this Wrapper
        Init(context.Context) error
        // Finalize should be called when all usage of this Wrapper is done
        Finalize(context.Context) error
    
        // Encrypt encrypts the given byte slice and puts information about the final result in the returned value. The second byte slice is to pass any additional authenticated data; this may or may not be used depending on the particular implementation.
        Encrypt(context.Context, []byte, []byte) (*EncryptedBlobInfo, error)
        // Decrypt takes in the value and decrypts it into the byte slice.  The byte slice is to pass any additional authenticated data; this may or may not be used depending on the particular implementation.
        Decrypt(context.Context, *EncryptedBlobInfo, []byte) ([]byte, error)
    }
    
    패키지는 인터페이스 유형 (어떤 것이 추상 함수를 연결하기 때문에) 이기 때문에 KMS마다 다른 방식으로 이 방법을 실현할 수 있습니다. (지정한 함수 서명을 사용합니다.)내 지식을 위해 AWS KMS를 선택해 설명하겠다.
    AWS KMS implementation looks something like this
    // Encrypt is used to encrypt the master key using the the AWS CMK.
    // This returns the ciphertext, and/or any errors from this
    // call. This should be called after the KMS client has been instantiated.
    func (k *Wrapper) Encrypt(_ context.Context, plaintext, aad []byte) (blob *wrapping.EncryptedBlobInfo, err error) {
        if plaintext == nil {
            return nil, fmt.Errorf("given plaintext for encryption is nil")
        }
    
        env, err := wrapping.NewEnvelope(nil).Encrypt(plaintext, aad)
        if err != nil {
            return nil, fmt.Errorf("error wrapping data: %w", err)
        }
    
        if k.client == nil {
            return nil, fmt.Errorf("nil client")
        }
    
        input := &kms.EncryptInput{
            KeyId:     aws.String(k.keyID),
            Plaintext: env.Key,
        }
        output, err := k.client.Encrypt(input)
        if err != nil {
            return nil, fmt.Errorf("error encrypting data: %w", err)
        }
    
        // Store the current key id
        //
        // When using a key alias, this will return the actual underlying key id
        // used for encryption.  This is helpful if you are looking to reencyrpt
        // your data when it is not using the latest key id. See these docs relating
        // to key rotation https://docs.aws.amazon.com/kms/latest/developerguide/rotate-keys.html
        keyID := aws.StringValue(output.KeyId)
        k.currentKeyID.Store(keyID)
    
        ret := &wrapping.EncryptedBlobInfo{
            Ciphertext: env.Ciphertext,
            IV:         env.IV,
            KeyInfo: &wrapping.KeyInfo{
                Mechanism: AWSKMSEnvelopeAESGCMEncrypt,
                // Even though we do not use the key id during decryption, store it
                // to know exactly the specific key used in encryption in case we
                // want to rewrap older entries
                KeyID:      keyID,
                WrappedKey: output.CiphertextBlob,
            },
        }
    
        return ret, nil
    }
    
    이 함수는 세 가지 주요 기능이 있다
  • 사용(무작위로 생성된) 데이터 암호화 키(DEK)
  • 암호화plaintext
  • 위 DEK 키를 KMS
  • 로 암호화
  • 나중에 저장 및 복호화할 수 있도록 모든 내용을 wrapping.EncryptedBlobInfo에 넣습니다.
    2 우리가 원본 코드를 볼 때 뒤의 절차는 매우 간단하다.암호화 단계는 passed into another function
    func (e *Envelope) Encrypt(plaintext []byte, aad []byte) (*EnvelopeInfo, error) {
        // Generate DEK
        key, err := uuid.GenerateRandomBytes(32)
        if err != nil {
            return nil, err
        }
        iv, err := uuid.GenerateRandomBytes(12)
        if err != nil {
            return nil, err
        }
        aead, err := e.aeadEncrypter(key)
        if err != nil {
            return nil, err
        }
    
        return &EnvelopeInfo{
            Ciphertext: aead.Seal(nil, iv, plaintext, aad),
            Key:        key,
            IV:         iv,
        }, nil
    }
    
    이 기능은 다음과 같습니다.
  • 암호화를 위한 무작위 DEK 생성
  • 그룹 암호 (이 예는 eas) 를 위한 무작위 nonce (초기화 벡터) 생성
  • 키 사용
  • 암호화plaintext그래서 이제 우리는 그것을 영구 저장에 필요한 모든 것에 넣을 수 있게 되었다.다음.

    암호화된 데이터를 2진법으로 서열화하여 다시 표시하다
    Vault는protobuf 바이너리 형식으로 데이터를 저장합니다.작업을 하기 위해서 구조 메시지를 지정하기 위해 .proto 파일이 필요합니다. protoc 이 파일을 프로그래밍 언어 구조/도움말 함수로 컴파일합니다.이 코드들은 언어 대상과 이진 데이터 사이의 서열화/반서열화에 도움이 될 것이다.We will look at the compiled version for Go
    // EncryptedBlobInfo contains information about the encrypted value along with
    // information about the key used to encrypt it
    type EncryptedBlobInfo struct {
        state         protoimpl.MessageState
        sizeCache     protoimpl.SizeCache
        unknownFields protoimpl.UnknownFields
    
        // Ciphertext is the encrypted bytes
        Ciphertext []byte `protobuf:"bytes,1,opt,name=ciphertext,proto3" json:"ciphertext,omitempty"`
        // IV is the initialization value used during encryption
        Iv []byte `protobuf:"bytes,2,opt,name=iv,proto3" json:"iv,omitempty"`
        // HMAC is the bytes of the HMAC, if any
        Hmac []byte `protobuf:"bytes,3,opt,name=hmac,proto3" json:"hmac,omitempty"`
        // Wrapped can be used by the client to indicate whether Ciphertext
        // actually contains wrapped data or not. This can be useful if you want to
        // reuse the same struct to pass data along before and after wrapping.
        Wrapped bool `protobuf:"varint,4,opt,name=wrapped,proto3" json:"wrapped,omitempty"`
        // KeyInfo contains information about the key that was used to create this value
        KeyInfo *KeyInfo `protobuf:"bytes,5,opt,name=key_info,json=keyInfo,proto3" json:"key_info,omitempty"`
        // ValuePath can be used by the client to store information about where the
        // value came from
        ValuePath string `protobuf:"bytes,6,opt,name=ValuePath,proto3" json:"ValuePath,omitempty"`
    }
    
    // KeyInfo contains information regarding which Wrapper key was used to
    // encrypt the entry
    type KeyInfo struct {
        state         protoimpl.MessageState
        sizeCache     protoimpl.SizeCache
        unknownFields protoimpl.UnknownFields
    
        // Mechanism is the method used by the wrapper to encrypt and sign the
        // data as defined by the wrapper.
        Mechanism     uint64 `protobuf:"varint,1,opt,name=Mechanism,proto3" json:"Mechanism,omitempty"`
        HMACMechanism uint64 `protobuf:"varint,2,opt,name=HMACMechanism,proto3" json:"HMACMechanism,omitempty"`
        // This is an opaque ID used by the wrapper to identify the specific key to
        // use as defined by the wrapper. This could be a version, key label, or
        // something else.
        KeyID     string `protobuf:"bytes,3,opt,name=KeyID,proto3" json:"KeyID,omitempty"`
        HMACKeyID string `protobuf:"bytes,4,opt,name=HMACKeyID,proto3" json:"HMACKeyID,omitempty"`
        // These value are used when generating our own data encryption keys
        // and encrypting them using the wrapper
        WrappedKey []byte `protobuf:"bytes,5,opt,name=WrappedKey,proto3" json:"WrappedKey,omitempty"`
        // Mechanism specific flags
        Flags uint64 `protobuf:"varint,6,opt,name=Flags,proto3" json:"Flags,omitempty"`
    }
    
    
    암호화된 후에 우리는 마땅히wrapping.EncryptedBlobInfo, 우리는 밭에 관심을 가져야 한다
    암호화 Blobinfo.암호: 암호화된 복구 키 값 포함
    암호화 Blobinfo.KeyInfo: DEK 키에 대한 정보 포함
    암호화 Blobinfo.열쇠 정보.키 ID: (KMS) DEK 암호화를 위한 키 ID
    암호화 Blobinfo.열쇠 정보.WrappedKey: DEK 암호화 값
    이 필드들을 이해하면 우리가 나중에 복호화하는 것을 도울 수 있다.서로 다른 언어를 사용하는 사람에게you could down load the definition 그리고 자신의 언어에 따라 조정한다

    스토리지 백엔드에 바이너리 데이터 쓰기
    위 단계를 완료하면 암호화 복구 키를 선택한 백엔드에 저장할 예정입니다by d.core.physical.Put(ctx, be) .
    마찬가지로 이것은 백엔드 메모리 구현에 특별히 정해진 인터페이스이다.사용하겠습니다 consul for the explanation.
    // Put is used to insert or update an entry
    func (c *ConsulBackend) Put(ctx context.Context, entry *physical.Entry) error {
        defer metrics.MeasureSince([]string{"consul", "put"}, time.Now())
    
        c.permitPool.Acquire()
        defer c.permitPool.Release()
    
        pair := &api.KVPair{
            Key:   c.path + entry.Key,
            Value: entry.Value,
        }
    
        writeOpts := &api.WriteOptions{}
        writeOpts = writeOpts.WithContext(ctx)
    
        _, err := c.kv.Put(pair, writeOpts)
        if err != nil {
            if strings.Contains(err.Error(), "Value exceeds") {
                return errwrap.Wrapf(fmt.Sprintf("%s: {{err}}", physical.ErrValueTooLarge), err)
            }
            return err
        }
        return nil
    }
    
    이 함수는 Concur KV API 클라이언트를 사용하여 키/값 쌍을 Concur 시스템에 쓰기만 하면 됩니다.

    끝맺다
    상술한 지식에 대해, 나는 우리가 상반된 과정을 하고, 스스로 저장 대상에서 복구 키를 얻을 수 있는지 보고 싶다.So I write a small program to do it . AWS KMS 및 영사 백엔드만 지원합니다.어떤 공헌도 환영받는다

    좋은 웹페이지 즐겨찾기