-
Notifications
You must be signed in to change notification settings - Fork 369
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Paul Schroeder (external expert on behalf of DB Netz) <[email protected]> Signed-off-by: Maximilian Blatt (external expert on behalf of DB Netz) <[email protected]> Co-authored-by: Maximilian Blatt (external expert on behalf of DB Netz) <[email protected]>
- Loading branch information
1 parent
4b2ce83
commit ab4a5c4
Showing
6 changed files
with
1,610 additions
and
186 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
/* | ||
Copyright 2019 The Crossplane Authors. | ||
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 dbinstance | ||
|
||
import ( | ||
"context" | ||
"strings" | ||
|
||
"github.com/crossplane/crossplane-runtime/pkg/resource" | ||
"github.com/pkg/errors" | ||
corev1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/types" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
|
||
svcapitypes "github.com/crossplane-contrib/provider-aws/apis/rds/v1alpha1" | ||
|
||
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" | ||
) | ||
|
||
// Publicly usable variables | ||
const ( | ||
PasswordCacheKey = "dbMasterUserPassword" | ||
RestoreFlagCacheKay = "dbRestoreState" | ||
|
||
RestoreStateRestored restoreSate = "RestoreStateRestored" | ||
RestoreStateNormal restoreSate = "RestoreStateNormal" | ||
|
||
ErrNoRetrievePasswordOrGenerate = "cannot retrieve password form masterUserPasswordSecretRef or generate a password" | ||
ErrNoMasterUserPasswordSecretRefNorAutogenerateNoRestore = "neither a masterUserPasswordSecretRef is given, nor password autogeneration was enabled, not a restore is performed" | ||
ErrCachePassword = "cannot cache password" | ||
ErrNoPasswordUpToDate = "cannot determine password up to date status" | ||
ErrGetCachedPassword = "cannot get cached password" | ||
ErrRetrievePasswordForUpdate = "cannot retrieve password for update" | ||
) | ||
|
||
const ( | ||
errGetSecret = "cannot get secret" | ||
errDeleteSecretF = "cannot delete secret %q in namespace %q" | ||
errGetCachedPassword = "cannot get cached password" | ||
errGetCachedRestoreInfo = "cannot get cached restore info" | ||
errGetMasterPassword = "cannot get master password" | ||
|
||
secretNamespace = "crossplane-system" | ||
) | ||
|
||
type restoreSate string | ||
|
||
// Cache caches the given key/value map to the corresponding cache secret | ||
func Cache(ctx context.Context, kube client.Client, mg resource.Managed, kv map[string]string) (sc *corev1.Secret, err error) { | ||
return updateOrCreateSecret(ctx, kube, getCachingSecretRef(mg), kv) | ||
} | ||
|
||
// DeleteCache removes the (secret) cache | ||
func DeleteCache(ctx context.Context, kube client.Client, mg resource.Managed) (err error) { | ||
ref := getCachingSecretRef(mg) | ||
|
||
secret := new(corev1.Secret) | ||
secret.Name = ref.Name | ||
secret.Namespace = ref.Namespace | ||
|
||
err = kube.Delete(ctx, secret) | ||
if resource.IgnoreNotFound(err) != nil { | ||
return errors.Wrapf(err, errDeleteSecretF, ref.Name, ref.Namespace) | ||
} | ||
return nil | ||
} | ||
|
||
// GetSecretValue fetches the referenced input secret key reference | ||
func GetSecretValue(ctx context.Context, kube client.Client, ref *xpv1.SecretKeySelector) (val string, err error) { | ||
secret, err := getSecret(ctx, kube, ref.SecretReference) | ||
if resource.IgnoreNotFound(err) != nil { | ||
return "", errors.Wrap(err, errGetSecret) | ||
} | ||
|
||
pwRaw := secret.Data[ref.Key] | ||
return string(pwRaw), nil | ||
} | ||
|
||
// GetDesiredPassword calculates the desired password from cache/masterPasswordSecretRef | ||
func GetDesiredPassword(ctx context.Context, kube client.Client, cr svcapitypes.RDSClusterOrInstance) (desiredPassword string, err error) { | ||
cachedPassword, err := getCachedPassword(ctx, kube, cr) | ||
if err != nil { | ||
return "", errors.Wrap(err, errGetCachedPassword) | ||
} | ||
if cr.GetMasterUserPasswordSecretRef() != nil { | ||
desiredPassword, err = GetSecretValue(ctx, kube, cr.GetMasterUserPasswordSecretRef()) | ||
if err != nil { | ||
return "", errors.Wrap(err, errGetMasterPassword) | ||
} | ||
} | ||
if desiredPassword == "" { | ||
desiredPassword = cachedPassword | ||
} | ||
return desiredPassword, nil | ||
} | ||
|
||
// PasswordUpToDate tell whether the password is up-to-date (depends on restore, masterPasswordSecretRef and cached password) | ||
func PasswordUpToDate(ctx context.Context, kube client.Client, cr svcapitypes.RDSClusterOrInstance) (upToDate bool, err error) { | ||
// (schroeder-paul): We are checking password changes after the database is ready. | ||
// - the restore scenario: if the new database has a different password than the old one, the old password will be | ||
// changed to the new one which was set or autogenerated (and cached) from the preCreate step. | ||
// - the user wants to change the password by changing the MasterUserPasswordSecretRef secret | ||
var desiredPassword string | ||
|
||
restoreInfo, err := getCachedRestoreInfo(ctx, kube, cr) | ||
if err != nil { | ||
return false, errors.Wrap(err, errGetCachedRestoreInfo) | ||
} | ||
cachedPassword, err := getCachedPassword(ctx, kube, cr) | ||
if err != nil { | ||
return false, errors.Wrap(err, errGetCachedPassword) | ||
} | ||
if cr.GetMasterUserPasswordSecretRef() != nil { | ||
desiredPassword, err = GetSecretValue(ctx, kube, cr.GetMasterUserPasswordSecretRef()) | ||
if err != nil { | ||
return false, errors.Wrap(err, errGetMasterPassword) | ||
} | ||
} | ||
|
||
wasRestored := restoreInfo == RestoreStateRestored | ||
passwordFromSecret := desiredPassword != "" | ||
secretPasswordMatchesCachedPassword := desiredPassword == cachedPassword | ||
newPasswordFromSecret := passwordFromSecret && !secretPasswordMatchesCachedPassword | ||
upToDate = !(newPasswordFromSecret || wasRestored) | ||
|
||
return upToDate, err | ||
} | ||
|
||
func getCachedRestoreInfo(ctx context.Context, kube client.Client, mg resource.Managed) (state restoreSate, err error) { | ||
secretKeyRef := &xpv1.SecretKeySelector{ | ||
SecretReference: getCachingSecretRef(mg), | ||
Key: RestoreFlagCacheKay, | ||
} | ||
restoreInfo, err := GetSecretValue(ctx, kube, secretKeyRef) | ||
|
||
switch restoreInfo { | ||
case string(RestoreStateRestored): | ||
state = RestoreStateRestored | ||
default: | ||
state = RestoreStateNormal | ||
} | ||
|
||
return state, err | ||
} | ||
|
||
func getCachedPassword(ctx context.Context, kube client.Client, mg resource.Managed) (pw string, err error) { | ||
secretKeyRef := &xpv1.SecretKeySelector{ | ||
SecretReference: getCachingSecretRef(mg), | ||
Key: PasswordCacheKey, | ||
} | ||
return GetSecretValue(ctx, kube, secretKeyRef) | ||
} | ||
|
||
func getCachingSecretRef(mg resource.Managed) xpv1.SecretReference { | ||
secretName := mg.GetObjectKind().GroupVersionKind().Kind + "." + string(mg.GetUID()) | ||
secretName = strings.ToLower(secretName) | ||
|
||
return xpv1.SecretReference{ | ||
Name: secretName, | ||
Namespace: secretNamespace, | ||
} | ||
} | ||
|
||
func updateOrCreateSecret(ctx context.Context, kube client.Client, ref xpv1.SecretReference, kv map[string]string) (*corev1.Secret, error) { | ||
data := make(map[string][]byte, len(kv)) | ||
for k, v := range kv { | ||
data[k] = []byte(v) | ||
} | ||
|
||
sc := &corev1.Secret{ | ||
Data: data, | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: ref.Name, | ||
Namespace: ref.Namespace, | ||
}, | ||
} | ||
err := resource.NewAPIPatchingApplicator(kube).Apply(ctx, sc) | ||
return sc, err | ||
} | ||
|
||
func getSecret(ctx context.Context, kube client.Client, ref xpv1.SecretReference) (*corev1.Secret, error) { | ||
secret := new(corev1.Secret) | ||
err := kube.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: ref.Namespace}, secret) | ||
return secret, err | ||
} |
Oops, something went wrong.