Skip to content

Commit

Permalink
Add package to detect platform properties
Browse files Browse the repository at this point in the history
Signed-off-by: John Strunk <jstrunk@redhat.com>
  • Loading branch information
JohnStrunk committed Sep 6, 2022
1 parent 7b1914c commit cdb0172
Show file tree
Hide file tree
Showing 16 changed files with 628 additions and 13 deletions.
1 change: 1 addition & 0 deletions .ci-scripts/yamlconfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ extends: default
ignore: |
bundle/**
config/**
controllers/platform/scc-crd.yml
hack/crds/*
helm/volsync/**
rules:
Expand Down
4 changes: 2 additions & 2 deletions bundle/manifests/volsync.backube_replicationdestinations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,8 @@ spec:
description: "Condition contains details for one aspect of the current
state of this API Resource. --- This struct is intended for direct
use as an array at the field path .status.conditions. For example,
type FooStatus struct{ // Represents the observations of a foo's
current state. // Known .status.conditions.type are: \"Available\",
\n type FooStatus struct{ // Represents the observations of a
foo's current state. // Known .status.conditions.type are: \"Available\",
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
// +listType=map // +listMapKey=type Conditions []metav1.Condition
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
Expand Down
4 changes: 2 additions & 2 deletions bundle/manifests/volsync.backube_replicationsources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,8 @@ spec:
description: "Condition contains details for one aspect of the current
state of this API Resource. --- This struct is intended for direct
use as an array at the field path .status.conditions. For example,
type FooStatus struct{ // Represents the observations of a foo's
current state. // Known .status.conditions.type are: \"Available\",
\n type FooStatus struct{ // Represents the observations of a
foo's current state. // Known .status.conditions.type are: \"Available\",
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
// +listType=map // +listMapKey=type Conditions []metav1.Condition
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
Expand Down
8 changes: 8 additions & 0 deletions bundle/manifests/volsync.clusterserviceversion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,14 @@ spec:
- patch
- update
- watch
- apiGroups:
- security.openshift.io
resources:
- securitycontextconstraints
verbs:
- get
- list
- watch
- apiGroups:
- security.openshift.io
resourceNames:
Expand Down
4 changes: 2 additions & 2 deletions config/crd/bases/volsync.backube_replicationdestinations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,8 @@ spec:
description: "Condition contains details for one aspect of the current
state of this API Resource. --- This struct is intended for direct
use as an array at the field path .status.conditions. For example,
type FooStatus struct{ // Represents the observations of a foo's
current state. // Known .status.conditions.type are: \"Available\",
\n type FooStatus struct{ // Represents the observations of a
foo's current state. // Known .status.conditions.type are: \"Available\",
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
// +listType=map // +listMapKey=type Conditions []metav1.Condition
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
Expand Down
4 changes: 2 additions & 2 deletions config/crd/bases/volsync.backube_replicationsources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -369,8 +369,8 @@ spec:
description: "Condition contains details for one aspect of the current
state of this API Resource. --- This struct is intended for direct
use as an array at the field path .status.conditions. For example,
type FooStatus struct{ // Represents the observations of a foo's
current state. // Known .status.conditions.type are: \"Available\",
\n type FooStatus struct{ // Represents the observations of a
foo's current state. // Known .status.conditions.type are: \"Available\",
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
// +listType=map // +listMapKey=type Conditions []metav1.Condition
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
Expand Down
8 changes: 8 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,14 @@ rules:
- patch
- update
- watch
- apiGroups:
- security.openshift.io
resources:
- securitycontextconstraints
verbs:
- get
- list
- watch
- apiGroups:
- security.openshift.io
resourceNames:
Expand Down
89 changes: 89 additions & 0 deletions controllers/platform/properties.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
Copyright 2022 The VolSync authors.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package platform

import (
"context"

"github.com/go-logr/logr"
ocpsecurityv1 "github.com/openshift/api/security/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// Properties contains properties about the environment in which we are running
type Properties struct {
IsOpenShift bool // True if we are running on OpenShift
HasSCCRestrictedV2 bool // True if the StorageContextConstraints "restricted-v2" exists
}

//+kubebuilder:rbac:groups=security.openshift.io,resources=securitycontextconstraints,verbs=get;list;watch

// Retrieves properties of the running cluster
func GetProperties(ctx context.Context, client client.Client, logger logr.Logger) (Properties, error) {
if err := ocpsecurityv1.AddToScheme(client.Scheme()); err != nil {
logger.Error(err, "unable to add scheme for security.openshift.io")
return Properties{}, err
}

var err error
p := Properties{}

if p.IsOpenShift, err = isOpenShift(ctx, client, logger); err != nil {
return Properties{}, err
}
if p.IsOpenShift {
if p.HasSCCRestrictedV2, err = hasSCCRestrictedV2(ctx, client, logger); err != nil {
return Properties{}, err
}
}
return p, nil
}

// Checks to determine whether this is OpenShift by looking for any SecurityContextConstraint objects
func isOpenShift(ctx context.Context, c client.Client, l logr.Logger) (bool, error) {
privSCC := ocpsecurityv1.SecurityContextConstraints{}
err := c.Get(ctx, client.ObjectKey{Name: "privileged"}, &privSCC)
if err == nil {
return true, nil
}
if apimeta.IsNoMatchError(err) || kerrors.IsNotFound(err) {
return false, nil
}
l.Error(err, "error while looking for privileged SCC")
return false, err
}

func hasSCCRestrictedV2(ctx context.Context, c client.Client, l logr.Logger) (bool, error) {
scc := ocpsecurityv1.SecurityContextConstraints{
ObjectMeta: metav1.ObjectMeta{
Name: "restricted-v2",
},
}
err := c.Get(ctx, client.ObjectKeyFromObject(&scc), &scc)
if err == nil {
return true, nil
}
if kerrors.IsNotFound(err) {
return false, nil
}
l.Error(err, "error while looking for restricted-v2 SCC")
return false, err
}
109 changes: 109 additions & 0 deletions controllers/platform/properties_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
Copyright 2021 The VolSync authors.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package platform

import (
"os"
"time"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

//"gopkg.in/yaml.v2"
ocpsecurityv1 "github.com/openshift/api/security/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/yaml"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
)

var logger = zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter))

var _ = Describe("A cluster w/o StorageContextConstraints", func() {
It("should NOT be detected as OpenShift", func() {
props, err := GetProperties(ctx, k8sClient, logger)
Expect(err).NotTo(HaveOccurred())
Expect(props.IsOpenShift).To(BeFalse())
Expect(props.HasSCCRestrictedV2).To(BeFalse())
})
})

var _ = Describe("A cluster w/ StorageContextConstraints", func() {
var sccCRD *apiextensionsv1.CustomResourceDefinition
var priv *ocpsecurityv1.SecurityContextConstraints
BeforeEach(func() {
// https://github.com/openshift/api/blob/master/security/v1/0000_03_security-openshift_01_scc.crd.yaml
bytes, err := os.ReadFile("scc-crd.yml")
// Make sure we successfully read the file
Expect(err).NotTo(HaveOccurred())
Expect(len(bytes)).To(BeNumerically(">", 0))
sccCRD = &apiextensionsv1.CustomResourceDefinition{}
err = yaml.Unmarshal(bytes, sccCRD)
Expect(err).NotTo(HaveOccurred())
// Parsed yaml correctly
Expect(sccCRD.Name).To(Equal("securitycontextconstraints.security.openshift.io"))
err = k8sClient.Create(ctx, sccCRD)
Expect(kerrors.IsAlreadyExists(err) || err == nil).To(BeTrue())
Eventually(func() bool {
// Getting a random SCC should return NotFound as opposed to NoKindMatchError
scc := ocpsecurityv1.SecurityContextConstraints{}
err := k8sClient.Get(ctx, client.ObjectKey{Name: "zzzz"}, &scc)
return kerrors.IsNotFound(err)
}, 5*time.Second).Should(BeTrue())
priv = &ocpsecurityv1.SecurityContextConstraints{
ObjectMeta: metav1.ObjectMeta{
Name: "privileged",
},
}
Expect(k8sClient.Create(ctx, priv)).To(Succeed())
})
AfterEach(func() {
Expect(k8sClient.Delete(ctx, priv)).To(Succeed())
})
It("should be detected as OpenShift", func() {
props, err := GetProperties(ctx, k8sClient, logger)
Expect(err).NotTo(HaveOccurred())
Expect(props.IsOpenShift).To(BeTrue())
})
It("should not detect restricted-v2", func() {
props, err := GetProperties(ctx, k8sClient, logger)
Expect(err).NotTo(HaveOccurred())
Expect(props.HasSCCRestrictedV2).To(BeFalse())
})
When("restricted-v2 exists", func() {
var rv2 *ocpsecurityv1.SecurityContextConstraints
BeforeEach(func() {
rv2 = &ocpsecurityv1.SecurityContextConstraints{
ObjectMeta: metav1.ObjectMeta{
Name: "restricted-v2",
},
}
Expect(k8sClient.Create(ctx, rv2)).To(Succeed())
})
AfterEach(func() {
Expect(k8sClient.Delete(ctx, rv2)).To(Succeed())
})
It("should be detected", func() {
props, err := GetProperties(ctx, k8sClient, logger)
Expect(err).NotTo(HaveOccurred())
Expect(props.HasSCCRestrictedV2).To(BeTrue())
})
})
})

0 comments on commit cdb0172

Please sign in to comment.