Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions api/v1/perconaservermysql_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"

Expand Down Expand Up @@ -359,6 +360,30 @@ type BackupStorageSpec struct {
RuntimeClassName *string `json:"runtimeClassName,omitempty"`
VerifyTLS *bool `json:"verifyTLS,omitempty"`
ContainerOptions *BackupContainerOptions `json:"containerOptions,omitempty"`
Encryption *EncryptionSpec `json:"encryption,omitempty"`
}

type EncryptionSpec struct {
Enabled bool `json:"enabled,omitempty"`
SecretName string `json:"secretName,omitempty"`
Key string `json:"key,omitempty"`
}

func (e *EncryptionSpec) ReadEncryptionKey(ctx context.Context, cl client.Reader, namespace string) (string, error) {
if !e.Enabled {
return "", fmt.Errorf("encryption is not enabled")
}

secret := &corev1.Secret{}
if err := cl.Get(ctx, types.NamespacedName{Namespace: namespace, Name: e.SecretName}, secret); err != nil {
return "", errors.Wrap(err, "get encryption secret")
}

value := secret.Data[e.Key]
if len(value) == 0 {
return "", fmt.Errorf("encryption key not found in secret")
}
Comment on lines +366 to +385
return string(value), nil
}

type BackupContainerOptions struct {
Expand Down
11 changes: 11 additions & 0 deletions api/v1/perconaservermysqlbackup_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type PerconaServerMySQLBackupSpec struct {
StorageName string `json:"storageName"`
SourcePod string `json:"sourcePod,omitempty"`
ContainerOptions *BackupContainerOptions `json:"containerOptions,omitempty"`
Encryption *EncryptionSpec `json:"encryption,omitempty"`
}

type BackupState string
Expand Down Expand Up @@ -155,6 +156,16 @@ func (b *PerconaServerMySQLBackup) GetContainerOptions(storage *BackupStorageSpe
return nil
}

func (b *PerconaServerMySQLBackup) GetEncryption(storage *BackupStorageSpec) *EncryptionSpec {
if b.Spec.Encryption != nil {
return b.Spec.Encryption
}
if storage != nil && storage.Encryption != nil {
return storage.Encryption
}
return nil
}
Comment on lines +159 to +167

//+kubebuilder:object:root=true

// PerconaServerMySQLBackupList contains a list of PerconaServerMySQLBackup
Expand Down
25 changes: 25 additions & 0 deletions api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions build/run-backup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"destination": "$(json_escape "${BACKUP_DEST}")",
"type": "$(json_escape "${STORAGE_TYPE}")",
"containerOptions": ${CONTAINER_OPTIONS},
"encryption": ${ENCRYPTION_OPTIONS},
"verifyTLS": $(json_escape "${VERIFY_TLS}"),
"s3": {
"bucket": "$(json_escape "${S3_BUCKET}")",
Expand All @@ -30,6 +31,7 @@
"verifyTLS": $(json_escape "${VERIFY_TLS}"),
"type": "$(json_escape "${STORAGE_TYPE}")",
"containerOptions": ${CONTAINER_OPTIONS},
"encryption": ${ENCRYPTION_OPTIONS},
"gcs": {
"bucket": "$(json_escape "${GCS_BUCKET}")",
"endpointUrl": "$(json_escape "${GCS_ENDPOINT}")",
Expand All @@ -47,6 +49,7 @@
"verifyTLS": $(json_escape "${VERIFY_TLS}"),
"type": "$(json_escape "${STORAGE_TYPE}")",
"containerOptions": ${CONTAINER_OPTIONS},
"encryption": ${ENCRYPTION_OPTIONS},
"azure": {
"containerName": "$(json_escape "${AZURE_CONTAINER_NAME}")",
"storageAccount": "$(json_escape "${AZURE_STORAGE_ACCOUNT}")",
Expand All @@ -62,7 +65,7 @@

# json_escape takes a string and replaces `\` to `\\` and `"` to `\"` to make it safe to insert provided argument into a json string
json_escape() {
escaped_backslash=${1//'\'/'\\'}

Check notice on line 68 in build/run-backup.sh

View workflow job for this annotation

GitHub Actions / shellcheck

[shellcheck] build/run-backup.sh#L68 <ShellCheck.SC1003>

Want to escape a single quote? echo 'This is how it'\''s done'.
Raw output
./build/run-backup.sh:68:26: info: Want to escape a single quote? echo 'This is how it'\''s done'. (ShellCheck.SC1003)

Check notice on line 68 in build/run-backup.sh

View workflow job for this annotation

GitHub Actions / shellcheck

[shellcheck] build/run-backup.sh#L68 <ShellCheck.SC1003>

Want to escape a single quote? echo 'This is how it'\''s done'.
Raw output
./build/run-backup.sh:68:31: info: Want to escape a single quote? echo 'This is how it'\''s done'. (ShellCheck.SC1003)
escaped_quotes=${escaped_backslash//'"'/'\"'}
echo -n "$escaped_quotes"
}
Expand Down
8 changes: 8 additions & 0 deletions build/run-restore.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
fi

run_s3() {
xbcloud get ${XBCLOUD_ARGS} "${BACKUP_DEST}" --storage=s3 --s3-bucket="${S3_BUCKET}"

Check notice on line 15 in build/run-restore.sh

View workflow job for this annotation

GitHub Actions / shellcheck

[shellcheck] build/run-restore.sh#L15 <ShellCheck.SC2086>

Double quote to prevent globbing and word splitting.
Raw output
./build/run-restore.sh:15:14: info: Double quote to prevent globbing and word splitting. (ShellCheck.SC2086)
}

run_gcs() {
xbcloud get ${XBCLOUD_ARGS} "${BACKUP_DEST}" --storage=google --google-bucket="${GCS_BUCKET}"

Check notice on line 19 in build/run-restore.sh

View workflow job for this annotation

GitHub Actions / shellcheck

[shellcheck] build/run-restore.sh#L19 <ShellCheck.SC2086>

Double quote to prevent globbing and word splitting.
Raw output
./build/run-restore.sh:19:14: info: Double quote to prevent globbing and word splitting. (ShellCheck.SC2086)
}

run_azure() {
xbcloud get ${XBCLOUD_ARGS} "${BACKUP_DEST}" --storage=azure

Check notice on line 23 in build/run-restore.sh

View workflow job for this annotation

GitHub Actions / shellcheck

[shellcheck] build/run-restore.sh#L23 <ShellCheck.SC2086>

Double quote to prevent globbing and word splitting.
Raw output
./build/run-restore.sh:23:14: info: Double quote to prevent globbing and word splitting. (ShellCheck.SC2086)
}

extract() {
Expand All @@ -43,6 +43,14 @@
"azure") run_azure | extract "${tmpdir}" ;;
esac

rm -rf "${tmpdir}/lost+found"

if [[ -n "${ENCRYPTION_KEY_FILE}" ]]; then
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[shfmt] reported by reviewdog 🐶

Suggested change
if [[ -n "${ENCRYPTION_KEY_FILE}" ]]; then
if [[ -n ${ENCRYPTION_KEY_FILE} ]]; then

echo "Decrypting backup with key file: ${ENCRYPTION_KEY_FILE}"
xtrabackup --decrypt=AES256 --encrypt-key-file="${ENCRYPTION_KEY_FILE}" --target-dir="${tmpdir}" --parallel="${PARALLEL}"
find "${tmpdir}" -name '*.xbcrypt' -delete
fi

local keyring=""
if [[ -f ${KEYRING_VAULT_PATH} ]]; then
if [[ ${XTRABACKUP_VERSION} == "8.0" ]]; then
Expand All @@ -50,7 +58,7 @@
keyring="--keyring-vault-config=${KEYRING_VAULT_PATH}"
elif [[ ${XTRABACKUP_VERSION} == "8.4" ]]; then
# PXB expects the config with a specific name
cp ${KEYRING_VAULT_PATH} /tmp/component_keyring_vault.cnf

Check notice on line 61 in build/run-restore.sh

View workflow job for this annotation

GitHub Actions / shellcheck

[shellcheck] build/run-restore.sh#L61 <ShellCheck.SC2086>

Double quote to prevent globbing and word splitting.
Raw output
./build/run-restore.sh:61:7: info: Double quote to prevent globbing and word splitting. (ShellCheck.SC2086)
echo "Using keyring vault component: /tmp/component_keyring_vault.cnf"
keyring="--component-keyring-config=/tmp/component_keyring_vault.cnf"
fi
Expand Down
43 changes: 41 additions & 2 deletions cmd/sidecar/handler/backup/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,18 @@ func (h *Handler) createBackupHandler(w http.ResponseWriter, req *http.Request)
http.Error(w, "backup failed", http.StatusInternalServerError)
return
}

encryptionKeyFile, err := h.createEncryptionKeyFile(req.Context(), &backupConf, ns)
if err != nil {
log.Error(err, "failed to create encryption key file")
http.Error(w, "backup failed", http.StatusInternalServerError)
return
}
defer os.Remove(encryptionKeyFile) //nolint:errcheck

g, gCtx := errgroup.WithContext(req.Context())

xtrabackup := exec.CommandContext(gCtx, "xtrabackup", xtrabackupArgs(string(backupUser), backupPass, &backupConf)...)
xtrabackup := exec.CommandContext(gCtx, "xtrabackup", xtrabackupArgs(string(backupUser), backupPass, &backupConf, encryptionKeyFile)...)
xtrabackup.Env = envs(backupConf)

xbOut, err := xtrabackup.StdoutPipe()
Expand Down Expand Up @@ -199,7 +208,7 @@ func (h *Handler) createBackupHandler(w http.ResponseWriter, req *http.Request)
log.Info("Backup finished successfully", "destination", backupConf.Destination, "storage", backupConf.Type)
}

func xtrabackupArgs(user, pass string, conf *xb.BackupConfig) []string {
func xtrabackupArgs(user, pass string, conf *xb.BackupConfig, encryptionKeyFile string) []string {
args := []string{
"--backup",
"--stream=xbstream",
Expand All @@ -212,6 +221,11 @@ func xtrabackupArgs(user, pass string, conf *xb.BackupConfig) []string {
if conf != nil && conf.ContainerOptions != nil {
args = append(args, conf.ContainerOptions.Args.Xtrabackup...)
}

if encryptionKeyFile != "" {
args = append(args, "--encrypt=AES256")
args = append(args, fmt.Sprintf("--encrypt-key-file=%s", encryptionKeyFile))
}
return args
}

Expand All @@ -224,6 +238,31 @@ func getClusterType() apiv1.ClusterType {
return apiv1.ClusterType(cType)
}

func (h *Handler) createEncryptionKeyFile(
ctx context.Context, cfg *xb.BackupConfig, ns string) (string, error) {
if cfg.Encryption == nil || !cfg.Encryption.Enabled {
return "", nil
}

key, err := cfg.Encryption.ReadEncryptionKey(ctx, h.k8sClient, ns)
if err != nil {
return "", errors.Wrap(err, "read encryption key")
}

tempFile, err := os.CreateTemp(os.TempDir(), "encryption-key-*.key")
if err != nil {
return "", errors.Wrap(err, "create temporary file")
}

if _, err := tempFile.WriteString(key); err != nil {
return "", errors.Wrap(err, "write key to file")
}
if err := tempFile.Close(); err != nil {
return "", errors.Wrap(err, "close file")
}
return tempFile.Name(), nil
Comment on lines +252 to +263
}

func (h *Handler) checkBackupMD5Size(ctx context.Context, cfg *xb.BackupConfig) error {
// xbcloud doesn't create md5 file for azure
if cfg.Type == apiv1.BackupStorageAzure {
Expand Down
22 changes: 19 additions & 3 deletions cmd/sidecar/handler/backup/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"sync/atomic"

"github.com/pkg/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"

xb "github.com/percona/percona-server-mysql-operator/pkg/xtrabackup"
"github.com/percona/percona-server-mysql-operator/pkg/xtrabackup/storage"
Expand All @@ -19,9 +21,15 @@ type Handler struct {
newStorageFunc storage.NewClientFunc
getNamespaceFunc func() (string, error)
deleteBackupFunc func(ctx context.Context, cfg *xb.BackupConfig, backupName string) error
k8sClient client.Client
}

func (h *Handler) init() {
func NewHandler() (*Handler, error) {
h := &Handler{}
return h, h.init()
}

func (h *Handler) init() error {
if h.deleteBackupFunc == nil {
h.deleteBackupFunc = deleteBackup
}
Expand All @@ -38,11 +46,19 @@ func (h *Handler) init() {
return string(ns), nil
}
}

if h.k8sClient == nil {
k8sClient, err := client.New(config.GetConfigOrDie(), client.Options{})
if err != nil {
Comment on lines +51 to +52
return errors.Wrap(err, "new k8s client")
}
h.k8sClient = k8sClient
}

return nil
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
h.init()

switch req.Method {
case http.MethodGet:
h.getBackupHandler(w, req)
Expand Down
5 changes: 0 additions & 5 deletions cmd/sidecar/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,9 @@ import (
"path/filepath"
"strings"

"github.com/percona/percona-server-mysql-operator/cmd/sidecar/handler/backup"
"github.com/percona/percona-server-mysql-operator/pkg/mysql"
)

func Backup() http.Handler {
return new(backup.Handler)
}

func LogsHandlerFunc(w http.ResponseWriter, req *http.Request) {
path := strings.Split(req.URL.Path, "/")
if len(path) < 3 {
Expand Down
9 changes: 8 additions & 1 deletion cmd/sidecar/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,24 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log/zap"

"github.com/percona/percona-server-mysql-operator/cmd/sidecar/handler"
"github.com/percona/percona-server-mysql-operator/cmd/sidecar/handler/backup"
"github.com/percona/percona-server-mysql-operator/pkg/mysql"
)

func startServer() *http.Server {
log := logf.Log.WithName("startServer")
mux := http.NewServeMux()

backupHandler, err := backup.NewHandler()
if err != nil {
log.Error(err, "failed to create backup handler")
os.Exit(1)
}

mux.HandleFunc("/health/", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "OK")
})
mux.Handle("/backup/", handler.Backup())
mux.Handle("/backup/", backupHandler)
mux.HandleFunc("/logs/", handler.LogsHandlerFunc)

srv := &http.Server{Addr: ":" + strconv.Itoa(mysql.SidecarHTTPPort), Handler: mux}
Expand Down
18 changes: 18 additions & 0 deletions config/crd/bases/ps.percona.com_perconaservermysqlbackups.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,15 @@ spec:
type: object
type: array
type: object
encryption:
properties:
enabled:
type: boolean
key:
type: string
secretName:
type: string
type: object
sourcePod:
type: string
storageName:
Expand Down Expand Up @@ -805,6 +814,15 @@ spec:
type: string
type: object
type: object
encryption:
properties:
enabled:
type: boolean
key:
type: string
secretName:
type: string
type: object
gcs:
properties:
bucket:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,15 @@ spec:
type: string
type: object
type: object
encryption:
properties:
enabled:
type: boolean
key:
type: string
secretName:
type: string
type: object
gcs:
properties:
bucket:
Expand Down
9 changes: 9 additions & 0 deletions config/crd/bases/ps.percona.com_perconaservermysqls.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2212,6 +2212,15 @@ spec:
type: string
type: object
type: object
encryption:
properties:
enabled:
type: boolean
key:
type: string
secretName:
type: string
type: object
gcs:
properties:
bucket:
Expand Down
1 change: 1 addition & 0 deletions deploy/backup/backup.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ spec:
# - --someflag=abc
# xtrabackup:
# - --someflag=abc
encryption: null
1 change: 1 addition & 0 deletions deploy/backup/restore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ spec:
# privileged: false
# runAsGroup: 1001
# runAsUser: 1001
# encryption: null
# labels:
# rack: rack-22
# nodeSelector:
Expand Down
Loading
Loading