Skip to content

Commit

Permalink
Merge pull request #124 from replicatedhq/divolgin/encryption-keys
Browse files Browse the repository at this point in the history
generate API encryption key
  • Loading branch information
divolgin authored Nov 4, 2019
2 parents 5f8f03d + 8e6f4f8 commit db79142
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 25 deletions.
83 changes: 83 additions & 0 deletions pkg/crypto/aes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package crypto

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"

"github.com/pkg/errors"
)

type AESCipher struct {
key []byte
cipher cipher.AEAD
nonce []byte
}

const keyLength = 24 // 192 bit

func NewAESCypher() (*AESCipher, error) {
key := make([]byte, keyLength)
if _, err := rand.Read(key); err != nil {
return nil, errors.Wrap(err, "failed to read key")
}

block, err := aes.NewCipher(key)
if err != nil {
return nil, errors.Wrap(err, "failed ro create new cipher")
}

gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, errors.Wrap(err, "failed to wrap cipher gcm")
}

nonce := make([]byte, gcm.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return nil, errors.Wrap(err, "failed to read nonce")
}

return &AESCipher{
key: key,
cipher: gcm,
nonce: nonce,
}, nil
}

func AESCipherFromString(data string) (*AESCipher, error) {
decoded, err := base64.StdEncoding.DecodeString(data)
if err != nil {
return nil, errors.Wrap(err, "failed to decode string")
}

if len(decoded) < keyLength {
return nil, errors.Errorf("cipher key is invalid: len=%d, expected %d", len(decoded), keyLength)
}

key := decoded[:keyLength]
block, err := aes.NewCipher(key)
if err != nil {
return nil, errors.Wrap(err, "failed to create cipher from data")
}

gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, errors.Wrap(err, "failed to wrap cipher gcm")
}

nonceLen := len(decoded) - keyLength
if nonceLen < gcm.NonceSize() {
return nil, errors.Errorf("cipher nonce is invalid: len=%d, expected %d", nonceLen, gcm.NonceSize())
}

return &AESCipher{
key: key,
cipher: gcm,
nonce: decoded[keyLength:],
}, nil
}

func (c *AESCipher) ToString() string {
return base64.StdEncoding.EncodeToString(append(c.key, c.nonce...))
}
13 changes: 13 additions & 0 deletions pkg/kotsadm/kotsadm.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type DeployOptions struct {
S3SecretKey string
JWT string
PostgresPassword string
APIEncryptionKey string
ServiceType string
NodePort int32
Hostname string
Expand Down Expand Up @@ -304,5 +305,17 @@ func readDeployOptionsFromCluster(namespace string, kubeconfig string, clientset
}
}

// API encryption key, read from the secret or create new password
encyptionSecret, err := getAPIEncryptionSecret(namespace, clientset)
if err != nil {
return nil, errors.Wrap(err, "failed to get postgres secret")
}
if encyptionSecret != nil {
key, ok := encyptionSecret.Data["encryptionKey"]
if ok {
deployOptions.APIEncryptionKey = string(key)
}
}

return &deployOptions, nil
}
59 changes: 59 additions & 0 deletions pkg/kotsadm/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/google/uuid"
"github.com/manifoldco/promptui"
"github.com/pkg/errors"
"github.com/replicatedhq/kots/pkg/crypto"
"golang.org/x/crypto/bcrypt"
corev1 "k8s.io/api/core/v1"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
Expand Down Expand Up @@ -45,6 +46,19 @@ func getSecretsYAML(deployOptions *DeployOptions) (map[string][]byte, error) {
}
docs["secret-shared-password.yaml"] = sharedPassword.Bytes()

if deployOptions.APIEncryptionKey == "" {
cipher, err := crypto.NewAESCypher()
if err != nil {
return nil, errors.Wrap(err, "failed to create new API encryption key")
}
deployOptions.APIEncryptionKey = cipher.ToString()
}
var apiEncryptionBuffer bytes.Buffer
if err := s.Encode(apiEncryptionKeySecret(deployOptions.Namespace, deployOptions.APIEncryptionKey), &apiEncryptionBuffer); err != nil {
return nil, errors.Wrap(err, "failed to marshal shared password secret")
}
docs["secret-api-encryption.yaml"] = apiEncryptionBuffer.Bytes()

var s3 bytes.Buffer
if deployOptions.S3SecretKey == "" {
deployOptions.S3SecretKey = uuid.New().String()
Expand Down Expand Up @@ -79,6 +93,10 @@ func ensureSecrets(deployOptions *DeployOptions, clientset *kubernetes.Clientset
return errors.Wrap(err, "failed to ensure s3 secret")
}

if err := ensureAPIEncryptionSecret(deployOptions, clientset); err != nil {
return errors.Wrap(err, "failed to ensure s3 secret")
}

return nil
}

Expand Down Expand Up @@ -247,3 +265,44 @@ func promptForSharedPassword() (string, error) {
}

}

func ensureAPIEncryptionSecret(deployOptions *DeployOptions, clientset *kubernetes.Clientset) error {
secret, err := getAPIEncryptionSecret(deployOptions.Namespace, clientset)
if err != nil {
return errors.Wrap(err, "failed to check for existing postgres secret")
}

if secret != nil {
if key, _ := secret.Data["encryptionKey"]; len(key) > 0 {
return nil
}
}

if deployOptions.APIEncryptionKey == "" {
cipher, err := crypto.NewAESCypher()
if err != nil {
return errors.Wrap(err, "failed to create new AES cipher")
}
deployOptions.APIEncryptionKey = cipher.ToString()
}

_, err = clientset.CoreV1().Secrets(deployOptions.Namespace).Create(apiEncryptionKeySecret(deployOptions.Namespace, deployOptions.APIEncryptionKey))
if err != nil {
return errors.Wrap(err, "failed to create API encryption secret")
}

return nil
}

func getAPIEncryptionSecret(namespace string, clientset *kubernetes.Clientset) (*corev1.Secret, error) {
apiSecret, err := clientset.CoreV1().Secrets(namespace).Get("kotsadm-encryption", metav1.GetOptions{})
if err != nil {
if kuberneteserrors.IsNotFound(err) {
return nil, nil
}

return nil, errors.Wrap(err, "failed to get postgres secret from cluster")
}

return apiSecret, nil
}
18 changes: 18 additions & 0 deletions pkg/kotsadm/secrets_objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,21 @@ func s3Secret(namespace string, accessKey string, secretKey string) *corev1.Secr

return secret
}

func apiEncryptionKeySecret(namespace string, key string) *corev1.Secret {
secret := &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "kotsadm-encryption",
Namespace: namespace,
},
Data: map[string][]byte{
"encryptionKey": []byte(key),
},
}

return secret
}
8 changes: 7 additions & 1 deletion pkg/upstream/admin-console.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,17 @@ func findFileAndReadSecret(secretName string, key string, renderDir string, file
for _, fi := range files {
data, err := ioutil.ReadFile(path.Join(renderDir, "admin-console", fi.Name()))
if err != nil {
return "", errors.Wrap(err, "failed to read file")
// TODO: log errors.Wrap(err, "failed to read file")
continue
}

decode := scheme.Codecs.UniversalDeserializer().Decode
obj, gvk, err := decode(data, nil, nil)
if err != nil {
// TODO: log err
continue
}

if gvk.Group != "" || gvk.Version != "v1" || gvk.Kind != "Secret" {
continue
}
Expand Down
28 changes: 4 additions & 24 deletions pkg/upstream/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@ package upstream

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"io/ioutil"
"os"
"path"

"github.com/pkg/errors"
kotsv1beta1 "github.com/replicatedhq/kots/kotskinds/apis/kots/v1beta1"
kotsscheme "github.com/replicatedhq/kots/kotskinds/client/kotsclientset/scheme"
"github.com/replicatedhq/kots/pkg/crypto"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/client-go/kubernetes/scheme"
Expand Down Expand Up @@ -154,29 +151,12 @@ func (u *Upstream) GetBaseDir(options WriteOptions) string {

func getEncryptionKey(previousInstallationContent []byte) (string, error) {
if previousInstallationContent == nil {
key := make([]byte, 24) // 192 bit
if _, err := rand.Read(key); err != nil {
return "", errors.Wrap(err, "failed to read key")
}

block, err := aes.NewCipher(key)
if err != nil {
return "", errors.Wrap(err, "failed ro create new cipher")
}

gcm, err := cipher.NewGCM(block)
cipher, err := crypto.NewAESCypher()
if err != nil {
return "", errors.Wrap(err, "failed to wrap cipher gcm")
}

nonce := make([]byte, gcm.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return "", errors.Wrap(err, "failed to read nonce")
return "", errors.Wrap(err, "failed to create new AES cipher")
}

newKey := base64.StdEncoding.EncodeToString(append(key, nonce...))

return newKey, nil
return cipher.ToString(), nil
}

kotsscheme.AddToScheme(scheme.Scheme)
Expand Down

0 comments on commit db79142

Please sign in to comment.