Skip to content

Commit 6a820e0

Browse files
authored
Merge pull request #568 from weng271190436/weweng/validateSshKey
Move AzureMachine SSHPublicKey validation to webhook
2 parents 5cf486a + 3f8c2d3 commit 6a820e0

File tree

7 files changed

+307
-20
lines changed

7 files changed

+307
-20
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha3
18+
19+
import (
20+
"crypto/rand"
21+
"crypto/rsa"
22+
23+
"github.com/pkg/errors"
24+
"golang.org/x/crypto/ssh"
25+
)
26+
27+
// SetDefaultSSHPublicKey sets the default SSHPublicKey for an AzureMachine
28+
func (m *AzureMachine) SetDefaultSSHPublicKey() error {
29+
sshKeyData := m.Spec.SSHPublicKey
30+
if sshKeyData == "" {
31+
privateKey, perr := rsa.GenerateKey(rand.Reader, 2048)
32+
if perr != nil {
33+
return errors.Wrap(perr, "Failed to generate private key")
34+
}
35+
36+
publicRsaKey, perr := ssh.NewPublicKey(&privateKey.PublicKey)
37+
if perr != nil {
38+
return errors.Wrap(perr, "Failed to generate public key")
39+
}
40+
m.Spec.SSHPublicKey = string(ssh.MarshalAuthorizedKey(publicRsaKey))
41+
}
42+
43+
return nil
44+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha3
18+
19+
import (
20+
"testing"
21+
22+
. "github.com/onsi/gomega"
23+
)
24+
25+
func TestAzureMachine_SetDefaultSSHPublicKey(t *testing.T) {
26+
g := NewWithT(t)
27+
28+
type test struct {
29+
machine *AzureMachine
30+
}
31+
32+
existingPublicKey := "testpublickey"
33+
publicKeyExistTest := test{machine: createMachineWithSSHPublicKey(t, existingPublicKey)}
34+
publicKeyNotExistTest := test{machine: createMachineWithSSHPublicKey(t, "")}
35+
36+
err := publicKeyExistTest.machine.SetDefaultSSHPublicKey()
37+
g.Expect(err).To(BeNil())
38+
g.Expect(publicKeyExistTest.machine.Spec.SSHPublicKey).To(Equal(existingPublicKey))
39+
40+
err = publicKeyNotExistTest.machine.SetDefaultSSHPublicKey()
41+
g.Expect(err).To(BeNil())
42+
g.Expect(publicKeyNotExistTest.machine.Spec.SSHPublicKey).To(Not(BeEmpty()))
43+
}
44+
45+
func createMachineWithSSHPublicKey(t *testing.T, sshPublicKey string) *AzureMachine {
46+
return &AzureMachine{
47+
Spec: AzureMachineSpec{
48+
SSHPublicKey: sshPublicKey,
49+
Image: &Image{
50+
SharedGallery: &AzureSharedGalleryImage{
51+
SubscriptionID: "SUB123",
52+
ResourceGroup: "RG123",
53+
Name: "NAME123",
54+
Gallery: "GALLERY1",
55+
Version: "1.0.0",
56+
},
57+
},
58+
},
59+
}
60+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha3
18+
19+
import (
20+
"golang.org/x/crypto/ssh"
21+
"k8s.io/apimachinery/pkg/util/validation/field"
22+
)
23+
24+
// ValidateSSHKey validates an SSHKey
25+
func ValidateSSHKey(sshKey string, fldPath *field.Path) field.ErrorList {
26+
allErrs := field.ErrorList{}
27+
28+
if _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(sshKey)); err != nil {
29+
allErrs = append(allErrs, field.Required(fldPath, "the SSH public key is not valid"))
30+
return allErrs
31+
}
32+
33+
return allErrs
34+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha3
18+
19+
import (
20+
"crypto/rand"
21+
"crypto/rsa"
22+
"testing"
23+
24+
. "github.com/onsi/gomega"
25+
"golang.org/x/crypto/ssh"
26+
"k8s.io/apimachinery/pkg/util/validation/field"
27+
)
28+
29+
func TestAzureMachine_ValidateSSHKey(t *testing.T) {
30+
g := NewWithT(t)
31+
32+
tests := []struct {
33+
name string
34+
sshKey string
35+
wantErr bool
36+
}{
37+
{
38+
name: "valid ssh key",
39+
sshKey: generateSSHPublicKey(),
40+
wantErr: false,
41+
},
42+
{
43+
name: "invalid ssh key",
44+
sshKey: "invalid ssh key",
45+
wantErr: true,
46+
},
47+
}
48+
49+
for _, tc := range tests {
50+
t.Run(tc.name, func(t *testing.T) {
51+
err := ValidateSSHKey(tc.sshKey, field.NewPath("sshPublicKey"))
52+
if tc.wantErr {
53+
g.Expect(err).ToNot(HaveLen(0))
54+
} else {
55+
g.Expect(err).To(HaveLen(0))
56+
}
57+
})
58+
}
59+
}
60+
61+
func generateSSHPublicKey() string {
62+
privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
63+
publicRsaKey, _ := ssh.NewPublicKey(&privateKey.PublicKey)
64+
return string(ssh.MarshalAuthorizedKey(publicRsaKey))
65+
}

api/v1alpha3/azuremachine_webhook.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,25 @@ func (m *AzureMachine) ValidateCreate() error {
4949
m.Name, errs)
5050
}
5151

52+
if errs := ValidateSSHKey(m.Spec.SSHPublicKey, field.NewPath("sshPublicKey")); len(errs) > 0 {
53+
return apierrors.NewInvalid(
54+
GroupVersion.WithKind("AzureMachine").GroupKind(),
55+
m.Name, errs)
56+
}
57+
5258
return nil
5359
}
5460

5561
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
5662
func (m *AzureMachine) ValidateUpdate(old runtime.Object) error {
5763
machinelog.Info("validate update", "name", m.Name)
5864

65+
if errs := ValidateSSHKey(m.Spec.SSHPublicKey, field.NewPath("sshPublicKey")); len(errs) > 0 {
66+
return apierrors.NewInvalid(
67+
GroupVersion.WithKind("AzureMachine").GroupKind(),
68+
m.Name, errs)
69+
}
70+
5971
return nil
6072
}
6173

@@ -65,3 +77,13 @@ func (m *AzureMachine) ValidateDelete() error {
6577

6678
return nil
6779
}
80+
81+
// Default implements webhookutil.defaulter so a webhook will be registered for the type
82+
func (m *AzureMachine) Default() {
83+
machinelog.Info("default", "name", m.Name)
84+
85+
err := m.SetDefaultSSHPublicKey()
86+
if err != nil {
87+
machinelog.Error(err, "SetDefaultSshPublicKey failed")
88+
}
89+
}

api/v1alpha3/azuremachine_webhook_test.go

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
. "github.com/onsi/gomega"
2323
)
2424

25+
var validSSHPublicKey = generateSSHPublicKey()
26+
2527
func TestAzureMachine_ValidateCreate(t *testing.T) {
2628
g := NewWithT(t)
2729

@@ -60,6 +62,21 @@ func TestAzureMachine_ValidateCreate(t *testing.T) {
6062
machine: createMachineWithImageByID(t, ""),
6163
wantErr: true,
6264
},
65+
{
66+
name: "azuremachine with valid SSHPublicKey",
67+
machine: createMachineWithSSHPublicKey(t, validSSHPublicKey),
68+
wantErr: false,
69+
},
70+
{
71+
name: "azuremachine without SSHPublicKey",
72+
machine: createMachineWithSSHPublicKey(t, ""),
73+
wantErr: true,
74+
},
75+
{
76+
name: "azuremachine with invalid SSHPublicKey",
77+
machine: createMachineWithSSHPublicKey(t, "invalid ssh key"),
78+
wantErr: true,
79+
},
6380
}
6481
for _, tc := range tests {
6582
t.Run(tc.name, func(t *testing.T) {
@@ -73,6 +90,64 @@ func TestAzureMachine_ValidateCreate(t *testing.T) {
7390
}
7491
}
7592

93+
func TestAzureMachine_ValidateUpdate(t *testing.T) {
94+
g := NewWithT(t)
95+
96+
tests := []struct {
97+
name string
98+
oldMachine *AzureMachine
99+
machine *AzureMachine
100+
wantErr bool
101+
}{
102+
{
103+
name: "azuremachine with valid SSHPublicKey",
104+
oldMachine: createMachineWithSSHPublicKey(t, ""),
105+
machine: createMachineWithSSHPublicKey(t, validSSHPublicKey),
106+
wantErr: false,
107+
},
108+
{
109+
name: "azuremachine without SSHPublicKey",
110+
oldMachine: createMachineWithSSHPublicKey(t, ""),
111+
machine: createMachineWithSSHPublicKey(t, ""),
112+
wantErr: true,
113+
},
114+
{
115+
name: "azuremachine with invalid SSHPublicKey",
116+
oldMachine: createMachineWithSSHPublicKey(t, ""),
117+
machine: createMachineWithSSHPublicKey(t, "invalid ssh key"),
118+
wantErr: true,
119+
},
120+
}
121+
for _, tc := range tests {
122+
t.Run(tc.name, func(t *testing.T) {
123+
err := tc.machine.ValidateUpdate(tc.oldMachine)
124+
if tc.wantErr {
125+
g.Expect(err).To(HaveOccurred())
126+
} else {
127+
g.Expect(err).NotTo(HaveOccurred())
128+
}
129+
})
130+
}
131+
}
132+
133+
func TestAzureMachine_Default(t *testing.T) {
134+
g := NewWithT(t)
135+
136+
type test struct {
137+
machine *AzureMachine
138+
}
139+
140+
existingPublicKey := validSSHPublicKey
141+
publicKeyExistTest := test{machine: createMachineWithSSHPublicKey(t, existingPublicKey)}
142+
publicKeyNotExistTest := test{machine: createMachineWithSSHPublicKey(t, "")}
143+
144+
publicKeyExistTest.machine.Default()
145+
g.Expect(publicKeyExistTest.machine.Spec.SSHPublicKey).To(Equal(existingPublicKey))
146+
147+
publicKeyNotExistTest.machine.Default()
148+
g.Expect(publicKeyNotExistTest.machine.Spec.SSHPublicKey).To(Not(BeEmpty()))
149+
}
150+
76151
func createMachineWithSharedImage(t *testing.T, subscriptionID, resourceGroup, name, gallery, version string) *AzureMachine {
77152
image := &Image{
78153
SharedGallery: &AzureSharedGalleryImage{
@@ -86,7 +161,8 @@ func createMachineWithSharedImage(t *testing.T, subscriptionID, resourceGroup, n
86161

87162
return &AzureMachine{
88163
Spec: AzureMachineSpec{
89-
Image: image,
164+
Image: image,
165+
SSHPublicKey: validSSHPublicKey,
90166
},
91167
}
92168

@@ -104,7 +180,8 @@ func createMachineWithtMarketPlaceImage(t *testing.T, publisher, offer, sku, ver
104180

105181
return &AzureMachine{
106182
Spec: AzureMachineSpec{
107-
Image: image,
183+
Image: image,
184+
SSHPublicKey: validSSHPublicKey,
108185
},
109186
}
110187
}
@@ -116,7 +193,8 @@ func createMachineWithImageByID(t *testing.T, imageID string) *AzureMachine {
116193

117194
return &AzureMachine{
118195
Spec: AzureMachineSpec{
119-
Image: image,
196+
Image: image,
197+
SSHPublicKey: validSSHPublicKey,
120198
},
121199
}
122200
}

0 commit comments

Comments
 (0)