Skip to content

Commit cbeaa36

Browse files
committed
Disallow fetching secrets from namespaces different from the host's one
The BareMetalHost CRD allows the UserData, MetaData, and NetworkData for the provisioned host to be specified as links to k8s Secrets. There are fields for both the Name and Namespace of the Secret, meaning that the baremetal-operator will read a Secret from any namespace. If a Secret contains the key "value" (or "userData", "metaData", or "networkData"), its corresponding value can be exfiltrated by a user provisioning a Host pointing to that Secret, then retrieving that data from the provisioned host. Authored-by: Zane Bitter <[email protected]> Co-Authored-By: Dmitry Tantsur <[email protected]> Signed-off-by: Tuomo Tanskanen <[email protected]>
1 parent 40eed52 commit cbeaa36

File tree

3 files changed

+60
-4
lines changed

3 files changed

+60
-4
lines changed

controllers/metal3.io/baremetalhost_controller_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ const (
4141
)
4242

4343
func newSecret(name string, data map[string]string) *corev1.Secret {
44+
return newSecretInNamespace(name, namespace, data)
45+
}
46+
47+
func newSecretInNamespace(name, namespace string, data map[string]string) *corev1.Secret {
4448
secretData := make(map[string][]byte)
4549
for k, v := range data {
4650
secretData[k] = []byte(base64.StdEncoding.EncodeToString([]byte(v)))

controllers/metal3.io/host_config_data.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ package controllers
22

33
import (
44
"github.com/go-logr/logr"
5-
corev1 "k8s.io/api/core/v1"
6-
"k8s.io/apimachinery/pkg/types"
7-
85
metal3api "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
96
"github.com/metal3-io/baremetal-operator/pkg/secretutils"
7+
"github.com/pkg/errors"
8+
corev1 "k8s.io/api/core/v1"
9+
"k8s.io/apimachinery/pkg/types"
1010
)
1111

1212
// hostConfigData is an implementation of host configuration data interface.
@@ -21,6 +21,10 @@ type hostConfigData struct {
2121
// parameter to detirmine which data to return in case secret contins multiple
2222
// keys.
2323
func (hcd *hostConfigData) getSecretData(name, namespace, dataKey string) (string, error) {
24+
if namespace != hcd.host.Namespace {
25+
return "", errors.Errorf("%s secret must be in same namespace as host %s/%s", dataKey, hcd.host.Namespace, hcd.host.Name)
26+
}
27+
2428
key := types.NamespacedName{
2529
Name: name,
2630
Namespace: namespace,

controllers/metal3.io/host_config_data_test.go

+49-1
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,54 @@ func TestProvisionWithHostConfig(t *testing.T) {
324324
ExpectedNetworkData: "",
325325
ErrNetworkData: true,
326326
},
327+
{
328+
Scenario: "user-data secret in different namespace",
329+
Host: newHost("host-user-data",
330+
&metal3api.BareMetalHostSpec{
331+
BMC: metal3api.BMCDetails{
332+
Address: "ipmi://192.168.122.1:6233",
333+
CredentialsName: defaultSecretName,
334+
},
335+
UserData: &corev1.SecretReference{
336+
Name: "user-data",
337+
Namespace: "other-namespace",
338+
},
339+
}),
340+
UserDataSecret: newSecretInNamespace("user-data", "other-namespace", map[string]string{"userData": "somedata"}),
341+
ErrUserData: true,
342+
},
343+
{
344+
Scenario: "meta-data secret in different namespace",
345+
Host: newHost("host-user-data",
346+
&metal3api.BareMetalHostSpec{
347+
BMC: metal3api.BMCDetails{
348+
Address: "ipmi://192.168.122.1:6233",
349+
CredentialsName: defaultSecretName,
350+
},
351+
MetaData: &corev1.SecretReference{
352+
Name: "meta-data",
353+
Namespace: "other-namespace",
354+
},
355+
}),
356+
NetworkDataSecret: newSecretInNamespace("meta-data", "other-namespace", map[string]string{"metaData": "key: value"}),
357+
ErrMetaData: true,
358+
},
359+
{
360+
Scenario: "network-data secret in different namespace",
361+
Host: newHost("host-user-data",
362+
&metal3api.BareMetalHostSpec{
363+
BMC: metal3api.BMCDetails{
364+
Address: "ipmi://192.168.122.1:6233",
365+
CredentialsName: defaultSecretName,
366+
},
367+
NetworkData: &corev1.SecretReference{
368+
Name: "net-data",
369+
Namespace: "other-namespace",
370+
},
371+
}),
372+
NetworkDataSecret: newSecretInNamespace("net-data", "other-namespace", map[string]string{"networkData": "key: value"}),
373+
ErrNetworkData: true,
374+
},
327375
}
328376

329377
for _, tc := range testCases {
@@ -379,7 +427,7 @@ func TestProvisionWithHostConfig(t *testing.T) {
379427
}
380428

381429
if actualMetaData != tc.ExpectedMetaData {
382-
t.Fatal(fmt.Errorf("Failed to assert MetaData. Expected '%s' got '%s'", actualMetaData, tc.ExpectedMetaData))
430+
t.Fatal(fmt.Errorf("Failed to assert MetaData. Expected '%s' got '%s'", tc.ExpectedMetaData, actualMetaData))
383431
}
384432
})
385433
}

0 commit comments

Comments
 (0)