-
Notifications
You must be signed in to change notification settings - Fork 88
/
password.go
144 lines (120 loc) · 4.58 KB
/
password.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package password
import (
"context"
"sync"
"time"
"github.com/pkg/errors"
"github.com/replicatedhq/kots/pkg/kotsadm/types"
"github.com/replicatedhq/kots/pkg/logger"
"github.com/replicatedhq/kots/pkg/store"
"github.com/replicatedhq/kots/pkg/util"
"golang.org/x/crypto/bcrypt"
corev1 "k8s.io/api/core/v1"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
// passwordLock - mutex to prevent multiple password changes at the same time
var passwordLock = sync.Mutex{}
var (
ErrCurrentPasswordDoesNotMatch = errors.New("The current password provided is incorrect.")
ErrNewPasswordTooShort = errors.New("The new password must be at least 6 characters.")
ErrNewPasswordShouldBeDifferent = errors.New("The new password must be different from the current password.")
)
// ValidatePasswordInput - will validate length and complexity of new password and check if it is different from current password
func ValidatePasswordInput(currentPassword string, newPassword string) error {
if len(newPassword) < 6 {
return ErrNewPasswordTooShort
}
if newPassword == currentPassword {
return ErrNewPasswordShouldBeDifferent
}
return nil
}
// ValidateCurrentPassword - will compare the password with the stored password and return an error if they don't match
func ValidateCurrentPassword(kotsStore store.Store, currentPassword string) error {
passwordLock.Lock()
defer passwordLock.Unlock()
shaBytes, err := kotsStore.GetSharedPasswordBcrypt()
if err != nil {
return errors.Wrap(err, "failed to get current shared password bcrypt")
}
if err := bcrypt.CompareHashAndPassword(shaBytes, []byte(currentPassword)); err != nil {
if err == bcrypt.ErrMismatchedHashAndPassword {
return ErrCurrentPasswordDoesNotMatch
}
return errors.Wrap(err, "failed to compare current password")
}
return nil
}
// ChangePassword - will change the password in the kotsadm secret
func ChangePassword(clientset *kubernetes.Clientset, namespace string, newPassword string) error {
passwordLock.Lock()
defer passwordLock.Unlock()
shaBytes, err := bcrypt.GenerateFromPassword([]byte(newPassword), 10)
if err != nil {
return errors.Wrap(err, "failed to generate new encrypted password")
}
if err := setSharedPasswordBcrypt(clientset, namespace, shaBytes); err != nil {
return errors.Wrap(err, "failed to set new shared password bcrypt")
}
return nil
}
// setSharedPasswordBcrypt - set the shared password bcrypt hash in the kotsadm secret
func setSharedPasswordBcrypt(clientset kubernetes.Interface, namespace string, bcryptPassword []byte) error {
secretData := map[string][]byte{
"passwordBcrypt": []byte(bcryptPassword),
"passwordUpdatedAt": []byte(time.Now().Format(time.RFC3339)),
}
existingPasswordSecret, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), util.PasswordSecretName, metav1.GetOptions{})
if err != nil {
if !kuberneteserrors.IsNotFound(err) {
return errors.Wrap(err, "failed to lookup secret")
}
newSecret := &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: util.PasswordSecretName,
Namespace: namespace,
},
Data: secretData,
}
_, err := clientset.CoreV1().Secrets(namespace).Create(context.TODO(), newSecret, metav1.CreateOptions{})
if err != nil {
return errors.Wrap(err, "failed to create secret")
}
} else {
existingPasswordSecret.Data = secretData
delete(existingPasswordSecret.Labels, "numAttempts")
delete(existingPasswordSecret.Labels, "lastFailure")
_, err := clientset.CoreV1().Secrets(namespace).Update(context.TODO(), existingPasswordSecret, metav1.UpdateOptions{})
if err != nil {
return errors.Wrap(err, "failed to update secret")
}
}
deleteAllSessions(clientset, namespace)
return nil
}
// deleteAllSessions - delete all sessions in the session secret, log errors if they occur
func deleteAllSessions(clientset kubernetes.Interface, namespace string) {
sessionSecret := &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: util.SessionsSecretName,
Namespace: namespace,
Labels: types.GetKotsadmLabels(),
},
Data: map[string][]byte{},
}
_, err := clientset.CoreV1().Secrets(namespace).Update(context.TODO(), sessionSecret, metav1.UpdateOptions{})
if err != nil {
// as the password is already changed, log the error but don't fail (false positive case)
logger.Errorf("failed to delete all sessions: %s", namespace, util.SessionsSecretName, err)
}
}