Skip to content

Commit

Permalink
oc adm migrate scc: implement a command for converting SCCs to PSPs.
Browse files Browse the repository at this point in the history
  • Loading branch information
php-coder committed Mar 7, 2018
1 parent 46258f8 commit 73c3fa0
Show file tree
Hide file tree
Showing 4 changed files with 961 additions and 0 deletions.
2 changes: 2 additions & 0 deletions pkg/oc/admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
migrateetcd "github.com/openshift/origin/pkg/oc/admin/migrate/etcd"
migrateimages "github.com/openshift/origin/pkg/oc/admin/migrate/images"
migratehpa "github.com/openshift/origin/pkg/oc/admin/migrate/legacyhpa"
migratescc "github.com/openshift/origin/pkg/oc/admin/migrate/scc"
migratestorage "github.com/openshift/origin/pkg/oc/admin/migrate/storage"
"github.com/openshift/origin/pkg/oc/admin/network"
"github.com/openshift/origin/pkg/oc/admin/node"
Expand Down Expand Up @@ -99,6 +100,7 @@ func NewCommandAdmin(name, fullName string, in io.Reader, out io.Writer, errout
migrateauthorization.NewCmdMigrateAuthorization("authorization", fullName+" "+migrate.MigrateRecommendedName+" authorization", f, in, out, errout),
migrateetcd.NewCmdMigrateTTLs("etcd-ttl", fullName+" "+migrate.MigrateRecommendedName+" etcd-ttl", f, in, out, errout),
migratehpa.NewCmdMigrateLegacyHPA("legacy-hpa", fullName+" "+migrate.MigrateRecommendedName+" legacy-hpa", f, in, out, errout),
migratescc.NewCmdMigrateSCC("scc", fullName+" "+migrate.MigrateRecommendedName+" scc", f, in, out, errout),
),
top.NewCommandTop(top.TopRecommendedName, fullName+" "+top.TopRecommendedName, f, out, errout),
image.NewCmdVerifyImageSignature(name, fullName+" "+image.VerifyRecommendedName, f, out, errout),
Expand Down
158 changes: 158 additions & 0 deletions pkg/oc/admin/migrate/scc/scc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package scc

import (
"errors"
"fmt"
"io"

extensions "k8s.io/api/extensions/v1beta1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kapi "k8s.io/kubernetes/pkg/apis/core"
rbacv1helper "k8s.io/kubernetes/pkg/apis/rbac/v1"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"

cmdutil "github.com/openshift/origin/pkg/cmd/util"
"github.com/openshift/origin/pkg/oc/cli/util/clientcmd"
securityapi "github.com/openshift/origin/pkg/security/apis/security"
securitytypedclient "github.com/openshift/origin/pkg/security/generated/internalclientset/typed/security/internalversion"

"github.com/spf13/cobra"
)

var (
internalMigrateSCCShort = "Converts SCCs to similar PSPs"
internalMigrateSCCLong = templates.LongDesc(`
Converts SecurityContextConstraints to equivalent PodSecurityPolicy objects.
NOTE: it does not modify anything by default.`)

internalMigrateSCCExample = templates.Examples(`
# Output to the console PodSecurityPolicy objects
%[1]s`)
)

type migrateSCCOptions struct {
sccClient securitytypedclient.SecurityContextConstraintsInterface
}

func NewCmdMigrateSCC(name, fullName string, f *clientcmd.Factory, in io.Reader, out, errout io.Writer) *cobra.Command {
options := &migrateSCCOptions{}
cmd := &cobra.Command{
Use: name,
Short: internalMigrateSCCShort,
Long: internalMigrateSCCLong,
Example: fmt.Sprintf(internalMigrateSCCExample, fullName),
Run: func(cmd *cobra.Command, args []string) {
kcmdutil.CheckErr(options.Complete(name, f, cmd, args))
kcmdutil.CheckErr(options.Validate())
kcmdutil.CheckErr(options.Run(cmd, f, out))
},
}
kcmdutil.AddPrinterFlags(cmd)
return cmd
}

func (o *migrateSCCOptions) Complete(name string, f *clientcmd.Factory, c *cobra.Command, args []string) error {
securityClient, err := f.OpenshiftInternalSecurityClient()
if err != nil {
return err
}
o.sccClient = securityClient.Security().SecurityContextConstraints()
return nil
}

func (o migrateSCCOptions) Validate() error {
if o.sccClient == nil {
return errors.New("a SCC client is required")
}
return nil
}

func (o migrateSCCOptions) Run(cmd *cobra.Command, f *clientcmd.Factory, out io.Writer) error {
sccs, err := o.sccClient.List(metav1.ListOptions{})
if err != nil {
return err
}

list := &kapi.List{}
for _, scc := range sccs.Items {
list.Items = append(list.Items, convertSccToPsp(&scc))
list.Items = append(list.Items, convertSccToClusterRole(&scc))
binding := convertSccToClusterRoleBinding(&scc)
if binding != nil {
list.Items = append(list.Items, binding)
}
}

mapper, _ := f.Object()
fn := cmdutil.VersionedPrintObject(f.PrintObject, cmd, mapper, out)
if err := fn(list); err != nil {
return err
}

return nil
}

func convertSccToPsp(scc *securityapi.SecurityContextConstraints) *extensions.PodSecurityPolicy {
// TODO: convert SCC.Priority *int32 (ERR: PSP doesn't have such field)
// TODO: convert SCC.AllowHostPorts bool (TODO: how to convert bool to []HostPortRange{Min:, Max:}?)
// TODO: handle SCC.Volumes: ["none"]

annotations := make(map[string]string)
extractSeccompProfiles(scc, annotations)
extractSysctls(scc, annotations)

// Note that SCCs have "kubernetes.io/description" annotation but looks like no one uses it, so we don't copy it over
return &extensions.PodSecurityPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: scc.Name,
Annotations: annotations,
},
Spec: extensions.PodSecurityPolicySpec{
Privileged: extractPrivileged(scc),
DefaultAddCapabilities: extractDefaultAddCapabilities(scc),
RequiredDropCapabilities: extractRequiredDropCapabilities(scc),
AllowedCapabilities: extractAllowedCapabilities(scc),
Volumes: extractVolumes(scc),
HostNetwork: extractHostNetwork(scc),
HostPID: extractHostPID(scc),
HostIPC: extractHostIPC(scc),
SELinux: extractSELinux(scc),
RunAsUser: extractRunAsUser(scc),
SupplementalGroups: extractSupplementalGroups(scc),
FSGroup: extractFSGroup(scc),
ReadOnlyRootFilesystem: extractReadOnlyRootFilesystem(scc),
AllowedFlexVolumes: extractAllowedFlexVolumes(scc),
// The following fields exist only in PSP, we leave them empty,
// so Kubernetes will use its default values
// - DefaultAllowPrivilegeEscalation
// - AllowPrivilegeEscalation (defaults to "true")
// - AllowedHostPaths (defaults to empty, that means "allow all")
},
}
}

// TODO: do we need to create a cluster role when users and groups are empty?
func convertSccToClusterRole(scc *securityapi.SecurityContextConstraints) *rbacv1.ClusterRole {
// TODO: use Rule() to improve error reporting
rule := rbacv1helper.NewRule("use").Groups("extensions").Resources("podsecuritypolicies").Names(scc.Name).RuleOrDie()

return &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{
Name: scc.Name,
},
Rules: []rbacv1.PolicyRule{rule},
}
}

func convertSccToClusterRoleBinding(scc *securityapi.SecurityContextConstraints) *rbacv1.ClusterRoleBinding {
if len(scc.Users) == 0 && len(scc.Groups) == 0 {
return nil
}
// TODO: use Binding() to improve error reporting
// TODO: handle SAs
binding := rbacv1helper.NewClusterBinding(scc.Name).Users(scc.Users...).Groups(scc.Groups...).BindingOrDie()
return &binding
}
Loading

0 comments on commit 73c3fa0

Please sign in to comment.