From a398a60d3772c94ed140d2fdc25f81c9feeca26a Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Fri, 12 Feb 2016 00:45:15 -0500 Subject: [PATCH 1/2] Get rid of the plugins/ dir --- pkg/cmd/infra/router/f5.go | 2 +- pkg/cmd/infra/router/template.go | 2 +- pkg/cmd/server/api/v1/types.go | 2 ++ pkg/cmd/server/origin/master.go | 2 +- {plugins => pkg}/route/allocation/doc.go | 0 {plugins => pkg}/route/allocation/simple/doc.go | 0 {plugins => pkg}/route/allocation/simple/plugin.go | 0 {plugins => pkg}/route/allocation/simple/plugin_test.go | 0 {plugins => pkg}/router/f5/f5.go | 0 {plugins => pkg}/router/f5/plugin.go | 0 {plugins => pkg}/router/f5/plugin_test.go | 0 {plugins => pkg}/router/f5/types.go | 0 {plugins => pkg}/router/template/certmanager.go | 0 {plugins => pkg}/router/template/certmanager_test.go | 0 {plugins => pkg}/router/template/fake.go | 0 {plugins => pkg}/router/template/plugin.go | 0 {plugins => pkg}/router/template/plugin_test.go | 0 {plugins => pkg}/router/template/router.go | 0 {plugins => pkg}/router/template/router_test.go | 0 {plugins => pkg}/router/template/test_scripts/reload-error | 0 {plugins => pkg}/router/template/types.go | 0 plugins/route/doc.go | 2 -- 22 files changed, 5 insertions(+), 5 deletions(-) rename {plugins => pkg}/route/allocation/doc.go (100%) rename {plugins => pkg}/route/allocation/simple/doc.go (100%) rename {plugins => pkg}/route/allocation/simple/plugin.go (100%) rename {plugins => pkg}/route/allocation/simple/plugin_test.go (100%) rename {plugins => pkg}/router/f5/f5.go (100%) rename {plugins => pkg}/router/f5/plugin.go (100%) rename {plugins => pkg}/router/f5/plugin_test.go (100%) rename {plugins => pkg}/router/f5/types.go (100%) rename {plugins => pkg}/router/template/certmanager.go (100%) rename {plugins => pkg}/router/template/certmanager_test.go (100%) rename {plugins => pkg}/router/template/fake.go (100%) rename {plugins => pkg}/router/template/plugin.go (100%) rename {plugins => pkg}/router/template/plugin_test.go (100%) rename {plugins => pkg}/router/template/router.go (100%) rename {plugins => pkg}/router/template/router_test.go (100%) rename {plugins => pkg}/router/template/test_scripts/reload-error (100%) rename {plugins => pkg}/router/template/types.go (100%) delete mode 100644 plugins/route/doc.go diff --git a/pkg/cmd/infra/router/f5.go b/pkg/cmd/infra/router/f5.go index 491640aac379..39d804ae73a9 100644 --- a/pkg/cmd/infra/router/f5.go +++ b/pkg/cmd/infra/router/f5.go @@ -13,8 +13,8 @@ import ( "github.com/openshift/origin/pkg/cmd/util" "github.com/openshift/origin/pkg/cmd/util/clientcmd" "github.com/openshift/origin/pkg/router/controller" + f5plugin "github.com/openshift/origin/pkg/router/f5" "github.com/openshift/origin/pkg/version" - f5plugin "github.com/openshift/origin/plugins/router/f5" ) const ( diff --git a/pkg/cmd/infra/router/template.go b/pkg/cmd/infra/router/template.go index c4587b785754..52e096f25b76 100644 --- a/pkg/cmd/infra/router/template.go +++ b/pkg/cmd/infra/router/template.go @@ -16,9 +16,9 @@ import ( "github.com/openshift/origin/pkg/cmd/util" "github.com/openshift/origin/pkg/cmd/util/clientcmd" "github.com/openshift/origin/pkg/router/controller" + templateplugin "github.com/openshift/origin/pkg/router/template" "github.com/openshift/origin/pkg/util/proc" "github.com/openshift/origin/pkg/version" - templateplugin "github.com/openshift/origin/plugins/router/template" ) const ( diff --git a/pkg/cmd/server/api/v1/types.go b/pkg/cmd/server/api/v1/types.go index fb0ab8c00fce..76c026bec425 100644 --- a/pkg/cmd/server/api/v1/types.go +++ b/pkg/cmd/server/api/v1/types.go @@ -274,6 +274,8 @@ type PolicyConfig struct { // RoutingConfig holds the necessary configuration options for routing to subdomains type RoutingConfig struct { // Subdomain is the suffix appended to $service.$namespace. to form the default route hostname + // DEPRECATED: This field is being replaced by routers setting their own defaults. This is the + // "default" route. Subdomain string `json:"subdomain"` } diff --git a/pkg/cmd/server/origin/master.go b/pkg/cmd/server/origin/master.go index b3885a9ba6d5..e25f47c400b7 100644 --- a/pkg/cmd/server/origin/master.go +++ b/pkg/cmd/server/origin/master.go @@ -102,7 +102,7 @@ import ( rolebindingstorage "github.com/openshift/origin/pkg/authorization/registry/rolebinding/policybased" "github.com/openshift/origin/pkg/authorization/registry/subjectaccessreview" configapi "github.com/openshift/origin/pkg/cmd/server/api" - routeplugin "github.com/openshift/origin/plugins/route/allocation/simple" + routeplugin "github.com/openshift/origin/pkg/route/allocation/simple" ) const ( diff --git a/plugins/route/allocation/doc.go b/pkg/route/allocation/doc.go similarity index 100% rename from plugins/route/allocation/doc.go rename to pkg/route/allocation/doc.go diff --git a/plugins/route/allocation/simple/doc.go b/pkg/route/allocation/simple/doc.go similarity index 100% rename from plugins/route/allocation/simple/doc.go rename to pkg/route/allocation/simple/doc.go diff --git a/plugins/route/allocation/simple/plugin.go b/pkg/route/allocation/simple/plugin.go similarity index 100% rename from plugins/route/allocation/simple/plugin.go rename to pkg/route/allocation/simple/plugin.go diff --git a/plugins/route/allocation/simple/plugin_test.go b/pkg/route/allocation/simple/plugin_test.go similarity index 100% rename from plugins/route/allocation/simple/plugin_test.go rename to pkg/route/allocation/simple/plugin_test.go diff --git a/plugins/router/f5/f5.go b/pkg/router/f5/f5.go similarity index 100% rename from plugins/router/f5/f5.go rename to pkg/router/f5/f5.go diff --git a/plugins/router/f5/plugin.go b/pkg/router/f5/plugin.go similarity index 100% rename from plugins/router/f5/plugin.go rename to pkg/router/f5/plugin.go diff --git a/plugins/router/f5/plugin_test.go b/pkg/router/f5/plugin_test.go similarity index 100% rename from plugins/router/f5/plugin_test.go rename to pkg/router/f5/plugin_test.go diff --git a/plugins/router/f5/types.go b/pkg/router/f5/types.go similarity index 100% rename from plugins/router/f5/types.go rename to pkg/router/f5/types.go diff --git a/plugins/router/template/certmanager.go b/pkg/router/template/certmanager.go similarity index 100% rename from plugins/router/template/certmanager.go rename to pkg/router/template/certmanager.go diff --git a/plugins/router/template/certmanager_test.go b/pkg/router/template/certmanager_test.go similarity index 100% rename from plugins/router/template/certmanager_test.go rename to pkg/router/template/certmanager_test.go diff --git a/plugins/router/template/fake.go b/pkg/router/template/fake.go similarity index 100% rename from plugins/router/template/fake.go rename to pkg/router/template/fake.go diff --git a/plugins/router/template/plugin.go b/pkg/router/template/plugin.go similarity index 100% rename from plugins/router/template/plugin.go rename to pkg/router/template/plugin.go diff --git a/plugins/router/template/plugin_test.go b/pkg/router/template/plugin_test.go similarity index 100% rename from plugins/router/template/plugin_test.go rename to pkg/router/template/plugin_test.go diff --git a/plugins/router/template/router.go b/pkg/router/template/router.go similarity index 100% rename from plugins/router/template/router.go rename to pkg/router/template/router.go diff --git a/plugins/router/template/router_test.go b/pkg/router/template/router_test.go similarity index 100% rename from plugins/router/template/router_test.go rename to pkg/router/template/router_test.go diff --git a/plugins/router/template/test_scripts/reload-error b/pkg/router/template/test_scripts/reload-error similarity index 100% rename from plugins/router/template/test_scripts/reload-error rename to pkg/router/template/test_scripts/reload-error diff --git a/plugins/router/template/types.go b/pkg/router/template/types.go similarity index 100% rename from plugins/router/template/types.go rename to pkg/router/template/types.go diff --git a/plugins/route/doc.go b/plugins/route/doc.go deleted file mode 100644 index 31e86d8a2e9a..000000000000 --- a/plugins/route/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package route contains all the route plugins. -package route From 241d02b8e89d492ad680559e56bb1549c9985c34 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Fri, 12 Feb 2016 01:22:47 -0500 Subject: [PATCH 2/2] Allow subdomain flag to create router Don't require user to have SCC access to create a router, clean up errors, check the appropriate scc permissions. Stop exposing hostPorts and use secrets for the default certificate. --- contrib/completions/bash/oadm | 2 + contrib/completions/bash/oc | 2 + contrib/completions/bash/openshift | 5 + pkg/cmd/admin/router/router.go | 484 +++++++++++++++++------------ pkg/cmd/infra/router/template.go | 39 +-- pkg/cmd/util/crypto.go | 25 ++ pkg/cmd/util/crypto_test.go | 20 ++ pkg/generate/app/env.go | 9 +- pkg/router/template/plugin.go | 21 +- pkg/router/template/router.go | 23 +- test/cmd/admin.sh | 15 - test/cmd/router.sh | 59 ++++ 12 files changed, 447 insertions(+), 257 deletions(-) create mode 100644 pkg/cmd/util/crypto_test.go create mode 100755 test/cmd/router.sh diff --git a/contrib/completions/bash/oadm b/contrib/completions/bash/oadm index 81e8649ce77e..1faf4a1bb885 100644 --- a/contrib/completions/bash/oadm +++ b/contrib/completions/bash/oadm @@ -2296,6 +2296,7 @@ _oadm_router() flags+=("--output-version=") flags+=("--ports=") flags+=("--replicas=") + flags+=("--secrets-as-env") flags+=("--selector=") flags+=("--service-account=") flags+=("--show-all") @@ -2305,6 +2306,7 @@ _oadm_router() flags+=("--stats-password=") flags+=("--stats-port=") flags+=("--stats-user=") + flags+=("--subdomain=") flags+=("--template=") two_word_flags+=("-t") flags+=("--type=") diff --git a/contrib/completions/bash/oc b/contrib/completions/bash/oc index f683259f9e0e..1c566dbe83dc 100644 --- a/contrib/completions/bash/oc +++ b/contrib/completions/bash/oc @@ -5359,6 +5359,7 @@ _oc_adm_router() flags+=("--output-version=") flags+=("--ports=") flags+=("--replicas=") + flags+=("--secrets-as-env") flags+=("--selector=") flags+=("--service-account=") flags+=("--show-all") @@ -5368,6 +5369,7 @@ _oc_adm_router() flags+=("--stats-password=") flags+=("--stats-port=") flags+=("--stats-user=") + flags+=("--subdomain=") flags+=("--template=") two_word_flags+=("-t") flags+=("--type=") diff --git a/contrib/completions/bash/openshift b/contrib/completions/bash/openshift index 11bfa5b92fe3..c33d2f19cee6 100644 --- a/contrib/completions/bash/openshift +++ b/contrib/completions/bash/openshift @@ -3006,6 +3006,7 @@ _openshift_admin_router() flags+=("--output-version=") flags+=("--ports=") flags+=("--replicas=") + flags+=("--secrets-as-env") flags+=("--selector=") flags+=("--service-account=") flags+=("--show-all") @@ -3015,6 +3016,7 @@ _openshift_admin_router() flags+=("--stats-password=") flags+=("--stats-port=") flags+=("--stats-user=") + flags+=("--subdomain=") flags+=("--template=") two_word_flags+=("-t") flags+=("--type=") @@ -11217,6 +11219,7 @@ _openshift_cli_adm_router() flags+=("--output-version=") flags+=("--ports=") flags+=("--replicas=") + flags+=("--secrets-as-env") flags+=("--selector=") flags+=("--service-account=") flags+=("--show-all") @@ -11226,6 +11229,7 @@ _openshift_cli_adm_router() flags+=("--stats-password=") flags+=("--stats-port=") flags+=("--stats-user=") + flags+=("--subdomain=") flags+=("--template=") two_word_flags+=("-t") flags+=("--type=") @@ -23730,6 +23734,7 @@ _openshift_infra_router() flags_completion+=("_filedir") flags+=("--context=") flags+=("--default-certificate=") + flags+=("--default-certificate-path=") flags+=("--fields=") flags+=("--hostname-template=") flags+=("--include-udp-endpoints") diff --git a/pkg/cmd/admin/router/router.go b/pkg/cmd/admin/router/router.go index a5f459e1449c..0c0fdbfdc76b 100644 --- a/pkg/cmd/admin/router/router.go +++ b/pkg/cmd/admin/router/router.go @@ -5,6 +5,7 @@ import ( "io" "math/rand" "os" + "path" "strconv" "strings" @@ -15,16 +16,17 @@ import ( "k8s.io/kubernetes/pkg/api/errors" kclient "k8s.io/kubernetes/pkg/client/unversioned" kclientcmd "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/serviceaccount" "k8s.io/kubernetes/pkg/util/intstr" - ocmdutil "github.com/openshift/origin/pkg/cmd/util" + authapi "github.com/openshift/origin/pkg/authorization/api" + cmdutil "github.com/openshift/origin/pkg/cmd/util" "github.com/openshift/origin/pkg/cmd/util/clientcmd" "github.com/openshift/origin/pkg/cmd/util/variable" configcmd "github.com/openshift/origin/pkg/config/cmd" - dapi "github.com/openshift/origin/pkg/deploy/api" + deployapi "github.com/openshift/origin/pkg/deploy/api" "github.com/openshift/origin/pkg/generate/app" "github.com/openshift/origin/pkg/security/admission" fileutil "github.com/openshift/origin/pkg/util/file" @@ -64,20 +66,35 @@ you have failover protection.` secretsVolumeName = "secret-volume" secretsPath = "/etc/secret-volume" + // this is the official private certificate path on Red Hat distros, and is at least structurally more + // correct than ubuntu based distributions which don't distinguish between public and private certs. + // Since Origin is CentOS based this is more likely to work. Ubuntu images should symlink this directory + // into /etc/ssl/certs to be compatible. + defaultCertificateDir = "/etc/pki/tls/private" + privkeySecretName = "external-host-private-key-secret" privkeyVolumeName = "external-host-private-key-volume" privkeyName = "router.pem" privkeyPath = secretsPath + "/" + privkeyName ) +var defaultCertificatePath = path.Join(defaultCertificateDir, "tls.crt") + // RouterConfig contains the configuration parameters necessary to // launch a router, including general parameters, type of router, and // type-specific parameters. type RouterConfig struct { + // Name is the router name, set as an argument + Name string + // Type is the router type, which determines which plugin to use (f5 // or template). Type string + // Subdomain is the subdomain served by this router. This may not be + // accepted by all routers. + Subdomain string + // ImageTemplate specifies the image from which the router will be created. ImageTemplate variable.ImageTemplate @@ -96,6 +113,9 @@ type RouterConfig struct { // or code 0 otherwise. DryRun bool + // SecretsAsEnv sets the credentials as env vars, instead of secrets. + SecretsAsEnv bool + // Credentials specifies the path to a .kubeconfig file with the credentials // with which the router may contact the master. Credentials string @@ -182,8 +202,11 @@ const ( // NewCmdRouter implements the OpenShift CLI router command. func NewCmdRouter(f *clientcmd.Factory, parentName, name string, out io.Writer) *cobra.Command { cfg := &RouterConfig{ + Name: "router", ImageTemplate: variable.NewDefaultImageTemplate(), + ServiceAccount: "router", + Labels: defaultLabel, Ports: defaultPorts, Replicas: 1, @@ -201,7 +224,7 @@ func NewCmdRouter(f *clientcmd.Factory, parentName, name string, out io.Writer) Run: func(cmd *cobra.Command, args []string) { err := RunCmdRouter(f, cmd, out, cfg, args) if err != errExit { - cmdutil.CheckErr(err) + kcmdutil.CheckErr(err) } else { os.Exit(1) } @@ -209,12 +232,14 @@ func NewCmdRouter(f *clientcmd.Factory, parentName, name string, out io.Writer) } cmd.Flags().StringVar(&cfg.Type, "type", "haproxy-router", "The type of router to use - if you specify --images this flag may be ignored.") + cmd.Flags().StringVar(&cfg.Subdomain, "subdomain", "", "The template for the route subdomain exposed by this router, used for routes that are not externally specified. E.g. '${name}-${namespace}.apps.mycompany.com'") cmd.Flags().StringVar(&cfg.ImageTemplate.Format, "images", cfg.ImageTemplate.Format, "The image to base this router on - ${component} will be replaced with --type") cmd.Flags().BoolVar(&cfg.ImageTemplate.Latest, "latest-images", cfg.ImageTemplate.Latest, "If true, attempt to use the latest images for the router instead of the latest release.") - cmd.Flags().StringVar(&cfg.Ports, "ports", cfg.Ports, "A comma delimited list of ports or port pairs to expose on the router pod. The default is set for HAProxy.") + cmd.Flags().StringVar(&cfg.Ports, "ports", cfg.Ports, "A comma delimited list of ports or port pairs to expose on the router pod. The default is set for HAProxy. Port pairs are applied to the service.") cmd.Flags().IntVar(&cfg.Replicas, "replicas", cfg.Replicas, "The replication factor of the router; commonly 2 when high availability is desired.") cmd.Flags().StringVar(&cfg.Labels, "labels", cfg.Labels, "A set of labels to uniquely identify the router and its components.") cmd.Flags().BoolVar(&cfg.DryRun, "dry-run", cfg.DryRun, "Exit with code 1 if the specified router does not exist.") + cmd.Flags().BoolVar(&cfg.SecretsAsEnv, "secrets-as-env", cfg.SecretsAsEnv, "Use environment variables for master secrets.") cmd.Flags().Bool("create", false, "deprecated; this is now the default behavior") cmd.Flags().StringVar(&cfg.Credentials, "credentials", "", "Path to a .kubeconfig file that will contain the credentials the router should use to contact the master.") cmd.Flags().StringVar(&cfg.DefaultCertificate, "default-cert", cfg.DefaultCertificate, "Optional path to a certificate file that be used as the default certificate. The file should contain the cert, key, and any CA certs necessary for the router to serve the certificate.") @@ -237,7 +262,7 @@ func NewCmdRouter(f *clientcmd.Factory, parentName, name string, out io.Writer) cmd.MarkFlagFilename("credentials", "kubeconfig") - cmdutil.AddPrinterFlags(cmd) + kcmdutil.AddPrinterFlags(cmd) return cmd } @@ -245,42 +270,27 @@ func NewCmdRouter(f *clientcmd.Factory, parentName, name string, out io.Writer) // generateSecretsConfig generates any Secret and Volume objects, such // as SSH private keys, that are necessary for the router container. func generateSecretsConfig(cfg *RouterConfig, kClient *kclient.Client, - namespace string) ([]*kapi.Secret, []kapi.Volume, []kapi.VolumeMount, + namespace string, defaultCert []byte) ([]*kapi.Secret, []kapi.Volume, []kapi.VolumeMount, error) { - secrets := []*kapi.Secret{} - volumes := []kapi.Volume{} - mounts := []kapi.VolumeMount{} + var secrets []*kapi.Secret + var volumes []kapi.Volume + var mounts []kapi.VolumeMount if len(cfg.ExternalHostPrivateKey) != 0 { privkeyData, err := fileutil.LoadData(cfg.ExternalHostPrivateKey) if err != nil { - return secrets, volumes, mounts, fmt.Errorf("error reading private key"+ - " for external host: %v", err) + return secrets, volumes, mounts, fmt.Errorf("error reading private key for external host: %v", err) } - serviceAccount, err := kClient.ServiceAccounts(namespace).Get(cfg.ServiceAccount) - if err != nil { - return secrets, volumes, mounts, fmt.Errorf("error looking up"+ - " service account %s: %v", cfg.ServiceAccount, err) - } - - privkeySecret := &kapi.Secret{ + secret := &kapi.Secret{ ObjectMeta: kapi.ObjectMeta{ Name: privkeySecretName, - Annotations: map[string]string{ - kapi.ServiceAccountNameKey: serviceAccount.Name, - kapi.ServiceAccountUIDKey: string(serviceAccount.UID), - }, }, Data: map[string][]byte{privkeyName: privkeyData}, } + secrets = append(secrets, secret) - secrets = append(secrets, privkeySecret) - } - - // We need a secrets volume and mount iff we have secrets. - if len(secrets) != 0 { - secretsVolume := kapi.Volume{ + volume := kapi.Volume{ Name: secretsVolumeName, VolumeSource: kapi.VolumeSource{ Secret: &kapi.SecretVolumeSource{ @@ -288,15 +298,51 @@ func generateSecretsConfig(cfg *RouterConfig, kClient *kclient.Client, }, }, } + volumes = append(volumes, volume) - secretsMount := kapi.VolumeMount{ + mount := kapi.VolumeMount{ Name: secretsVolumeName, ReadOnly: true, MountPath: secretsPath, } + mounts = append(mounts, mount) + } - volumes = []kapi.Volume{secretsVolume} - mounts = []kapi.VolumeMount{secretsMount} + if len(defaultCert) > 0 { + keys, err := cmdutil.PrivateKeysFromPEM(defaultCert) + if err != nil { + return nil, nil, nil, err + } + if len(keys) == 0 { + return nil, nil, nil, fmt.Errorf("the default cert must contain a private key") + } + secret := &kapi.Secret{ + ObjectMeta: kapi.ObjectMeta{ + Name: fmt.Sprintf("%s-certs", cfg.Name), + }, + Type: kapi.SecretTypeTLS, + Data: map[string][]byte{ + kapi.TLSCertKey: defaultCert, + kapi.TLSPrivateKeyKey: keys, + }, + } + secrets = append(secrets, secret) + volume := kapi.Volume{ + Name: "server-certificate", + VolumeSource: kapi.VolumeSource{ + Secret: &kapi.SecretVolumeSource{ + SecretName: secret.Name, + }, + }, + } + volumes = append(volumes, volume) + + mount := kapi.VolumeMount{ + Name: volume.Name, + ReadOnly: true, + MountPath: defaultCertificateDir, + } + mounts = append(mounts, mount) } return secrets, volumes, mounts, nil @@ -380,43 +426,41 @@ func generateMetricsExporterContainer(cfg *RouterConfig, env app.Environment) *k // RunCmdRouter contains all the necessary functionality for the // OpenShift CLI router command. func RunCmdRouter(f *clientcmd.Factory, cmd *cobra.Command, out io.Writer, cfg *RouterConfig, args []string) error { - var name string switch len(args) { case 0: - name = "router" + // uses default value case 1: - name = args[0] + cfg.Name = args[0] default: - return cmdutil.UsageError(cmd, "You may pass zero or one arguments to provide a name for the router") + return kcmdutil.UsageError(cmd, "You may pass zero or one arguments to provide a name for the router") } + name := cfg.Name if len(cfg.StatsUsername) > 0 { if strings.Contains(cfg.StatsUsername, ":") { - return cmdutil.UsageError(cmd, "username %s must not contain ':'", cfg.StatsUsername) + return kcmdutil.UsageError(cmd, "username %s must not contain ':'", cfg.StatsUsername) } } ports, err := app.ContainerPortsFromString(cfg.Ports) if err != nil { - glog.Fatal(err) + return fmt.Errorf("unable to parse --ports: %v", err) } - // For the host networking case, ensure the ports match. - if cfg.HostNetwork { - for i := 0; i < len(ports); i++ { - if ports[i].ContainerPort != ports[i].HostPort { - return cmdutil.UsageError(cmd, "For host networking mode, please ensure that the container [%v] and host [%v] ports match", ports[i].ContainerPort, ports[i].HostPort) - } + // For the host networking case, ensure the ports match. Otherwise, remove host ports + for i := 0; i < len(ports); i++ { + if cfg.HostNetwork && ports[i].HostPort != 0 && ports[i].ContainerPort != ports[i].HostPort { + return fmt.Errorf("when using host networking mode, container port %d and host port %d must be equal", ports[i].ContainerPort, ports[i].HostPort) } } if cfg.StatsPort > 0 { - ports = append(ports, kapi.ContainerPort{ + port := kapi.ContainerPort{ Name: "stats", - HostPort: cfg.StatsPort, ContainerPort: cfg.StatsPort, Protocol: kapi.ProtocolTCP, - }) + } + ports = append(ports, port) } label := map[string]string{"router": name} @@ -426,7 +470,7 @@ func RunCmdRouter(f *clientcmd.Factory, cmd *cobra.Command, out io.Writer, cfg * glog.Fatal(err) } if len(remove) > 0 { - return cmdutil.UsageError(cmd, "You may not pass negative labels in %q", cfg.Labels) + return kcmdutil.UsageError(cmd, "You may not pass negative labels in %q", cfg.Labels) } label = valid } @@ -438,7 +482,7 @@ func RunCmdRouter(f *clientcmd.Factory, cmd *cobra.Command, out io.Writer, cfg * glog.Fatal(err) } if len(remove) > 0 { - return cmdutil.UsageError(cmd, "You may not pass negative labels in selector %q", cfg.Selector) + return kcmdutil.UsageError(cmd, "You may not pass negative labels in selector %q", cfg.Selector) } nodeSelector = valid } @@ -454,7 +498,7 @@ func RunCmdRouter(f *clientcmd.Factory, cmd *cobra.Command, out io.Writer, cfg * return fmt.Errorf("error getting client: %v", err) } - _, output, err := cmdutil.PrinterForCommand(cmd) + _, output, err := kcmdutil.PrinterForCommand(cmd) if err != nil { return fmt.Errorf("unable to configure printer: %v", err) } @@ -469,26 +513,29 @@ func RunCmdRouter(f *clientcmd.Factory, cmd *cobra.Command, out io.Writer, cfg * generate = true } } + if !generate { + fmt.Fprintf(out, "Router %q service exists\n", name) + return nil + } - if generate { - if cfg.DryRun && !output { - return fmt.Errorf("router %q does not exist (no service)", name) - } - - if len(cfg.ServiceAccount) == 0 { - return fmt.Errorf("router could not be created; you must specify a service account with --service-account") - } + if cfg.DryRun && !output { + return fmt.Errorf("router %q does not exist (no service)", name) + } - err := validateServiceAccount(kClient, namespace, cfg.ServiceAccount) - if err != nil { - return fmt.Errorf("router could not be created; %v", err) - } + if len(cfg.ServiceAccount) == 0 { + return fmt.Errorf("you must specify a service account for the router with --service-account") + } - // create new router - if len(cfg.Credentials) == 0 { - return fmt.Errorf("router could not be created; you must specify a .kubeconfig file path containing credentials for connecting the router to the master with --credentials") - } + if err := validateServiceAccount(kClient, namespace, cfg.ServiceAccount, cfg.HostNetwork); err != nil { + return fmt.Errorf("router could not be created; %v", err) + } + // create new router + secretEnv := app.Environment{} + switch { + case len(cfg.Credentials) == 0 && len(cfg.ServiceAccount) == 0: + return fmt.Errorf("router could not be created; you must specify a .kubeconfig file path containing credentials for connecting the router to the master with --credentials") + case len(cfg.Credentials) > 0: clientConfigLoadingRules := &kclientcmd.ClientConfigLoadingRules{ExplicitPath: cfg.Credentials, Precedence: []string{}} credentials, err := clientConfigLoadingRules.Load() if err != nil { @@ -505,154 +552,181 @@ func RunCmdRouter(f *clientcmd.Factory, cmd *cobra.Command, out io.Writer, cfg * if config.Insecure { insecure = "true" } + secretEnv.Add(app.Environment{ + "OPENSHIFT_MASTER": config.Host, + "OPENSHIFT_CA_DATA": string(config.CAData), + "OPENSHIFT_KEY_DATA": string(config.KeyData), + "OPENSHIFT_CERT_DATA": string(config.CertData), + "OPENSHIFT_INSECURE": insecure, + }) + } + createServiceAccount := len(cfg.ServiceAccount) > 0 && len(cfg.Credentials) == 0 - defaultCert, err := fileutil.LoadData(cfg.DefaultCertificate) - if err != nil { - return fmt.Errorf("router could not be created; error reading default certificate file: %v", err) - } - - if len(cfg.StatsPassword) == 0 { - cfg.StatsPassword = generateStatsPassword() - fmt.Fprintf(out, "password for stats user %s has been set to %s\n", cfg.StatsUsername, cfg.StatsPassword) - } - - env := app.Environment{ - "OPENSHIFT_MASTER": config.Host, - "OPENSHIFT_CA_DATA": string(config.CAData), - "OPENSHIFT_KEY_DATA": string(config.KeyData), - "OPENSHIFT_CERT_DATA": string(config.CertData), - "OPENSHIFT_INSECURE": insecure, - "DEFAULT_CERTIFICATE": string(defaultCert), - "ROUTER_SERVICE_NAME": name, - "ROUTER_SERVICE_NAMESPACE": namespace, - "ROUTER_EXTERNAL_HOST_HOSTNAME": cfg.ExternalHost, - "ROUTER_EXTERNAL_HOST_USERNAME": cfg.ExternalHostUsername, - "ROUTER_EXTERNAL_HOST_PASSWORD": cfg.ExternalHostPassword, - "ROUTER_EXTERNAL_HOST_HTTP_VSERVER": cfg.ExternalHostHttpVserver, - "ROUTER_EXTERNAL_HOST_HTTPS_VSERVER": cfg.ExternalHostHttpsVserver, - "ROUTER_EXTERNAL_HOST_INSECURE": strconv.FormatBool(cfg.ExternalHostInsecure), - "ROUTER_EXTERNAL_HOST_PARTITION_PATH": cfg.ExternalHostPartitionPath, - "ROUTER_EXTERNAL_HOST_PRIVKEY": privkeyPath, - "STATS_PORT": strconv.Itoa(cfg.StatsPort), - "STATS_USERNAME": cfg.StatsUsername, - "STATS_PASSWORD": cfg.StatsPassword, - } - - updatePercent := int(-25) - - secrets, volumes, mounts, err := generateSecretsConfig(cfg, kClient, - namespace) - if err != nil { - return fmt.Errorf("router could not be created: %v", err) - } - - livenessProbe := generateLivenessProbeConfig(cfg, ports) - readinessProbe := generateReadinessProbeConfig(cfg, ports) - - containers := []kapi.Container{ - { - Name: "router", - Image: image, - Ports: ports, - Env: env.List(), - LivenessProbe: livenessProbe, - ReadinessProbe: readinessProbe, - ImagePullPolicy: kapi.PullIfNotPresent, - VolumeMounts: mounts, - }, - } + defaultCert, err := fileutil.LoadData(cfg.DefaultCertificate) + if err != nil { + return fmt.Errorf("router could not be created; error reading default certificate file: %v", err) + } - if cfg.StatsPort > 0 && cfg.ExposeMetrics { - pc := generateMetricsExporterContainer(cfg, env) - if pc != nil { - containers = append(containers, *pc) - } + if len(cfg.StatsPassword) == 0 { + cfg.StatsPassword = generateStatsPassword() + if !output { + fmt.Fprintf(cmd.Out(), "info: password for stats user %s has been set to %s\n", cfg.StatsUsername, cfg.StatsPassword) } + } - objects := []runtime.Object{ - &dapi.DeploymentConfig{ - ObjectMeta: kapi.ObjectMeta{ - Name: name, - Labels: label, - }, - Spec: dapi.DeploymentConfigSpec{ - Strategy: dapi.DeploymentStrategy{ - Type: dapi.DeploymentStrategyTypeRolling, - RollingParams: &dapi.RollingDeploymentStrategyParams{UpdatePercent: &updatePercent}, - }, - Replicas: cfg.Replicas, - Selector: label, - Triggers: []dapi.DeploymentTriggerPolicy{ - {Type: dapi.DeploymentTriggerOnConfigChange}, - }, - Template: &kapi.PodTemplateSpec{ - ObjectMeta: kapi.ObjectMeta{Labels: label}, - Spec: kapi.PodSpec{ - SecurityContext: &kapi.PodSecurityContext{ - HostNetwork: cfg.HostNetwork, - }, - ServiceAccountName: cfg.ServiceAccount, - NodeSelector: nodeSelector, - Containers: containers, - Volumes: volumes, - }, - }, - }, - }, + env := app.Environment{ + "ROUTER_SUBDOMAIN": cfg.Subdomain, + "ROUTER_SERVICE_NAME": name, + "ROUTER_SERVICE_NAMESPACE": namespace, + "ROUTER_EXTERNAL_HOST_HOSTNAME": cfg.ExternalHost, + "ROUTER_EXTERNAL_HOST_USERNAME": cfg.ExternalHostUsername, + "ROUTER_EXTERNAL_HOST_PASSWORD": cfg.ExternalHostPassword, + "ROUTER_EXTERNAL_HOST_HTTP_VSERVER": cfg.ExternalHostHttpVserver, + "ROUTER_EXTERNAL_HOST_HTTPS_VSERVER": cfg.ExternalHostHttpsVserver, + "ROUTER_EXTERNAL_HOST_INSECURE": strconv.FormatBool(cfg.ExternalHostInsecure), + "ROUTER_EXTERNAL_HOST_PARTITION_PATH": cfg.ExternalHostPartitionPath, + "ROUTER_EXTERNAL_HOST_PRIVKEY": privkeyPath, + "STATS_PORT": strconv.Itoa(cfg.StatsPort), + "STATS_USERNAME": cfg.StatsUsername, + "STATS_PASSWORD": cfg.StatsPassword, + } + env.Add(secretEnv) + if len(defaultCert) > 0 { + if cfg.SecretsAsEnv { + env.Add(app.Environment{"DEFAULT_CERTIFICATE": string(defaultCert)}) + } else { + // TODO: make --credentials create secrets and bypass service account + env.Add(app.Environment{"DEFAULT_CERTIFICATE_PATH": defaultCertificatePath}) } + } - if len(secrets) != 0 { - serviceAccount, err := kClient.ServiceAccounts(namespace).Get(cfg.ServiceAccount) - if err != nil { - return fmt.Errorf("error looking up service account %s: %v", - cfg.ServiceAccount, err) - } + secrets, volumes, mounts, err := generateSecretsConfig(cfg, kClient, namespace, defaultCert) + if err != nil { + return fmt.Errorf("router could not be created: %v", err) + } - for _, secret := range secrets { - objects = append(objects, secret) + livenessProbe := generateLivenessProbeConfig(cfg, ports) + readinessProbe := generateReadinessProbeConfig(cfg, ports) - serviceAccount.Secrets = append(serviceAccount.Secrets, - kapi.ObjectReference{Name: secret.Name}) - } + exposedPorts := make([]kapi.ContainerPort, len(ports)) + copy(exposedPorts, ports) + for i := range exposedPorts { + exposedPorts[i].HostPort = 0 + } + containers := []kapi.Container{ + { + Name: "router", + Image: image, + Ports: exposedPorts, + Env: env.List(), + LivenessProbe: livenessProbe, + ReadinessProbe: readinessProbe, + ImagePullPolicy: kapi.PullIfNotPresent, + VolumeMounts: mounts, + }, + } - _, err = kClient.ServiceAccounts(namespace).Update(serviceAccount) - if err != nil { - return fmt.Errorf("error adding secret key to service account %s: %v", - cfg.ServiceAccount, err) - } + if cfg.StatsPort > 0 && cfg.ExposeMetrics { + pc := generateMetricsExporterContainer(cfg, env) + if pc != nil { + containers = append(containers, *pc) } + } - objects = app.AddServices(objects, true) - // TODO: label all created objects with the same label - router= - list := &kapi.List{Items: objects} - - if output { - list.Items, err = ocmdutil.ConvertItemsForDisplayFromDefaultCommand(cmd, list.Items) - if err != nil { - return err - } - - if err := f.PrintObject(cmd, list, out); err != nil { - return fmt.Errorf("Unable to print object: %v", err) + objects := []runtime.Object{} + for _, s := range secrets { + objects = append(objects, s) + } + if createServiceAccount { + objects = append(objects, + &kapi.ServiceAccount{ObjectMeta: kapi.ObjectMeta{Name: cfg.ServiceAccount}}, + &authapi.ClusterRoleBinding{ + ObjectMeta: kapi.ObjectMeta{Name: fmt.Sprintf("router-%s-role", cfg.Name)}, + Subjects: []kapi.ObjectReference{ + { + Kind: "ServiceAccount", + Name: cfg.ServiceAccount, + Namespace: namespace, + }, + }, + RoleRef: kapi.ObjectReference{ + Kind: "ClusterRole", + Name: "system:router", + }, + }, + ) + } + updatePercent := int(-25) + objects = append(objects, &deployapi.DeploymentConfig{ + ObjectMeta: kapi.ObjectMeta{ + Name: name, + Labels: label, + }, + Spec: deployapi.DeploymentConfigSpec{ + Strategy: deployapi.DeploymentStrategy{ + Type: deployapi.DeploymentStrategyTypeRolling, + RollingParams: &deployapi.RollingDeploymentStrategyParams{UpdatePercent: &updatePercent}, + }, + Replicas: cfg.Replicas, + Selector: label, + Triggers: []deployapi.DeploymentTriggerPolicy{ + {Type: deployapi.DeploymentTriggerOnConfigChange}, + }, + Template: &kapi.PodTemplateSpec{ + ObjectMeta: kapi.ObjectMeta{Labels: label}, + Spec: kapi.PodSpec{ + SecurityContext: &kapi.PodSecurityContext{ + HostNetwork: cfg.HostNetwork, + }, + ServiceAccountName: cfg.ServiceAccount, + NodeSelector: nodeSelector, + Containers: containers, + Volumes: volumes, + }, + }, + }, + }) + + objects = app.AddServices(objects, false) + // set the service port to the provided hostport value + for i := range objects { + switch t := objects[i].(type) { + case *kapi.Service: + for j, servicePort := range t.Spec.Ports { + for _, targetPort := range ports { + if targetPort.ContainerPort == servicePort.Port && targetPort.HostPort != 0 { + t.Spec.Ports[j].Port = targetPort.HostPort + } + } } - return nil } + } + // TODO: label all created objects with the same label - router= + list := &kapi.List{Items: objects} - mapper, typer := f.Factory.Object() - bulk := configcmd.Bulk{ - Mapper: mapper, - Typer: typer, - RESTClientFactory: f.Factory.ClientForMapping, - - After: configcmd.NewPrintNameOrErrorAfter(mapper, cmdutil.GetFlagString(cmd, "output") == "name", "created", out, cmd.Out()), + if output { + list.Items, err = cmdutil.ConvertItemsForDisplayFromDefaultCommand(cmd, list.Items) + if err != nil { + return err } - if errs := bulk.Create(list, namespace); len(errs) != 0 { - return errExit + + if err := f.PrintObject(cmd, list, out); err != nil { + return fmt.Errorf("unable to print object: %v", err) } return nil } - fmt.Fprintf(out, "Router %q service exists\n", name) + mapper, typer := f.Factory.Object() + bulk := configcmd.Bulk{ + Mapper: mapper, + Typer: typer, + RESTClientFactory: f.Factory.ClientForMapping, + + After: configcmd.NewPrintNameOrErrorAfter(mapper, kcmdutil.GetFlagString(cmd, "output") == "name", "created", out, cmd.Out()), + } + if errs := bulk.Create(list, namespace); len(errs) != 0 { + return errExit + } return nil } @@ -668,22 +742,30 @@ func generateStatsPassword() string { return strings.Join(password, "") } -func validateServiceAccount(kClient *kclient.Client, ns string, sa string) error { +func validateServiceAccount(client *kclient.Client, ns string, serviceAccount string, hostNetwork bool) error { + if !hostNetwork { + return nil + } + // get cluster sccs - sccList, err := kClient.SecurityContextConstraints().List(kapi.ListOptions{}) + sccList, err := client.SecurityContextConstraints().List(kapi.ListOptions{}) if err != nil { - return fmt.Errorf("unable to validate service account %v", err) + if !errors.IsUnauthorized(err) { + return fmt.Errorf("could not retrieve list of security constraints to verify service account %q: %v", serviceAccount, err) + } + return nil } // get set of sccs applicable to the service account - userInfo := serviceaccount.UserInfo(ns, sa, "") + userInfo := serviceaccount.UserInfo(ns, serviceAccount, "") for _, scc := range sccList.Items { if admission.ConstraintAppliesTo(&scc, userInfo) { - if scc.AllowHostPorts { + switch { + case hostNetwork && scc.AllowHostNetwork: return nil } } } - return fmt.Errorf("unable to validate service account, host ports are forbidden") + return fmt.Errorf("service account %q is not allowed to access the host network on nodes, needs access via a security context constraint", serviceAccount) } diff --git a/pkg/cmd/infra/router/template.go b/pkg/cmd/infra/router/template.go index 52e096f25b76..85a53caf2b03 100644 --- a/pkg/cmd/infra/router/template.go +++ b/pkg/cmd/infra/router/template.go @@ -45,19 +45,21 @@ type TemplateRouterOptions struct { } type TemplateRouter struct { - RouterName string - WorkingDir string - TemplateFile string - ReloadScript string - ReloadInterval time.Duration - DefaultCertificate string - RouterService *ktypes.NamespacedName + RouterName string + WorkingDir string + TemplateFile string + ReloadScript string + ReloadInterval time.Duration + DefaultCertificate string + DefaultCertificatePath string + RouterService *ktypes.NamespacedName } func (o *TemplateRouter) Bind(flag *pflag.FlagSet) { flag.StringVar(&o.RouterName, "name", util.Env("ROUTER_SERVICE_NAME", "public"), "The name the router will identify itself with in the route status") flag.StringVar(&o.WorkingDir, "working-dir", "/var/lib/containers/router", "The working directory for the router plugin") - flag.StringVar(&o.DefaultCertificate, "default-certificate", util.Env("DEFAULT_CERTIFICATE", ""), "A path to default certificate to use for routes that don't expose a TLS server cert; in PEM format") + flag.StringVar(&o.DefaultCertificate, "default-certificate", util.Env("DEFAULT_CERTIFICATE", ""), "The contents of a default certificate to use for routes that don't expose a TLS server cert; in PEM format") + flag.StringVar(&o.DefaultCertificatePath, "default-certificate-path", util.Env("DEFAULT_CERTIFICATE_PATH", ""), "A path to default certificate to use for routes that don't expose a TLS server cert; in PEM format") flag.StringVar(&o.TemplateFile, "template", util.Env("TEMPLATE_FILE", ""), "The path to the template file to use") flag.StringVar(&o.ReloadScript, "reload", util.Env("RELOAD_SCRIPT", ""), "The path to the reload script to use") @@ -157,16 +159,17 @@ func (o *TemplateRouterOptions) Validate() error { // Run launches a template router using the provided options. It never exits. func (o *TemplateRouterOptions) Run() error { pluginCfg := templateplugin.TemplatePluginConfig{ - WorkingDir: o.WorkingDir, - TemplatePath: o.TemplateFile, - ReloadScriptPath: o.ReloadScript, - ReloadInterval: o.ReloadInterval, - DefaultCertificate: o.DefaultCertificate, - StatsPort: o.StatsPort, - StatsUsername: o.StatsUsername, - StatsPassword: o.StatsPassword, - PeerService: o.RouterService, - IncludeUDP: o.RouterSelection.IncludeUDP, + WorkingDir: o.WorkingDir, + TemplatePath: o.TemplateFile, + ReloadScriptPath: o.ReloadScript, + ReloadInterval: o.ReloadInterval, + DefaultCertificate: o.DefaultCertificate, + DefaultCertificatePath: o.DefaultCertificatePath, + StatsPort: o.StatsPort, + StatsUsername: o.StatsUsername, + StatsPassword: o.StatsPassword, + PeerService: o.RouterService, + IncludeUDP: o.RouterSelection.IncludeUDP, } templatePlugin, err := templateplugin.NewTemplatePlugin(pluginCfg) diff --git a/pkg/cmd/util/crypto.go b/pkg/cmd/util/crypto.go index 5e36eef2a7a3..742f3b58cdfd 100644 --- a/pkg/cmd/util/crypto.go +++ b/pkg/cmd/util/crypto.go @@ -1,6 +1,7 @@ package util import ( + "bytes" "crypto/x509" "encoding/pem" "errors" @@ -73,3 +74,27 @@ func CertificatesFromPEM(pemCerts []byte) ([]*x509.Certificate, error) { } return certs, nil } + +// PrivateKeysFromPEM extracts all blocks recognized as private keys into an output PEM encoded byte array, +// or returns an error. If there are no private keys it will return an empty byte buffer. +func PrivateKeysFromPEM(pemCerts []byte) ([]byte, error) { + buf := &bytes.Buffer{} + for len(pemCerts) > 0 { + var block *pem.Block + block, pemCerts = pem.Decode(pemCerts) + if block == nil { + break + } + if len(block.Headers) != 0 { + continue + } + switch block.Type { + // defined in OpenSSL pem.h + case "RSA PRIVATE KEY", "PRIVATE KEY", "ANY PRIVATE KEY", "DSA PRIVATE KEY", "ENCRYPTED PRIVATE KEY", "EC PRIVATE KEY": + if err := pem.Encode(buf, block); err != nil { + return nil, err + } + } + } + return buf.Bytes(), nil +} diff --git a/pkg/cmd/util/crypto_test.go b/pkg/cmd/util/crypto_test.go new file mode 100644 index 000000000000..70a54e9ddae0 --- /dev/null +++ b/pkg/cmd/util/crypto_test.go @@ -0,0 +1,20 @@ +package util + +import ( + "io/ioutil" + "testing" +) + +func TestPrivateKeysFromPEM(t *testing.T) { + data, err := ioutil.ReadFile("../../../images/router/haproxy-base/conf/default_pub_keys.pem") + if err != nil { + t.Fatal(err) + } + result, err := PrivateKeysFromPEM(data) + if err != nil { + t.Fatal(err) + } + if len(result) == 0 { + t.Fatalf("didn't extract results: %s", result) + } +} diff --git a/pkg/generate/app/env.go b/pkg/generate/app/env.go index fd94d18446a7..70d9c5930e47 100644 --- a/pkg/generate/app/env.go +++ b/pkg/generate/app/env.go @@ -16,12 +16,17 @@ func NewEnvironment(envs ...map[string]string) Environment { return envs[0] } out := make(Environment) + out.Add(envs...) + return out +} + +// Add adds the environment variables to the current environment +func (e Environment) Add(envs ...map[string]string) { for _, env := range envs { for k, v := range env { - out[k] = v + e[k] = v } } - return out } // List sorts and returns all the environment variables diff --git a/pkg/router/template/plugin.go b/pkg/router/template/plugin.go index 60d6dda2c451..ca52eac03505 100644 --- a/pkg/router/template/plugin.go +++ b/pkg/router/template/plugin.go @@ -31,16 +31,17 @@ func newDefaultTemplatePlugin(router routerInterface, includeUDP bool) *Template } type TemplatePluginConfig struct { - WorkingDir string - TemplatePath string - ReloadScriptPath string - ReloadInterval time.Duration - DefaultCertificate string - StatsPort int - StatsUsername string - StatsPassword string - IncludeUDP bool - PeerService *ktypes.NamespacedName + WorkingDir string + TemplatePath string + ReloadScriptPath string + ReloadInterval time.Duration + DefaultCertificate string + DefaultCertificatePath string + StatsPort int + StatsUsername string + StatsPassword string + IncludeUDP bool + PeerService *ktypes.NamespacedName } // routerInterface controls the interaction of the plugin with the underlying router implementation diff --git a/pkg/router/template/router.go b/pkg/router/template/router.go index ccccaa31204a..c4cf556fd0af 100644 --- a/pkg/router/template/router.go +++ b/pkg/router/template/router.go @@ -79,16 +79,17 @@ type templateRouter struct { // templateRouterCfg holds all configuration items required to initialize the template router type templateRouterCfg struct { - dir string - templates map[string]*template.Template - reloadScriptPath string - reloadInterval time.Duration - defaultCertificate string - statsUser string - statsPassword string - statsPort int - peerEndpointsKey string - includeUDP bool + dir string + templates map[string]*template.Template + reloadScriptPath string + reloadInterval time.Duration + defaultCertificate string + defaultCertificatePath string + statsUser string + statsPassword string + statsPort int + peerEndpointsKey string + includeUDP bool } // templateConfig is a subset of the templateRouter information that should be passed to the template for generating @@ -137,7 +138,7 @@ func newTemplateRouter(cfg templateRouterCfg) (*templateRouter, error) { state: make(map[string]ServiceUnit), certManager: certManager, defaultCertificate: cfg.defaultCertificate, - defaultCertificatePath: "", + defaultCertificatePath: cfg.defaultCertificatePath, statsUser: cfg.statsUser, statsPassword: cfg.statsPassword, statsPort: cfg.statsPort, diff --git a/test/cmd/admin.sh b/test/cmd/admin.sh index f2309083e965..f9e96e06b17a 100755 --- a/test/cmd/admin.sh +++ b/test/cmd/admin.sh @@ -201,21 +201,6 @@ os::cmd::expect_success 'oadm new-project recreated-project --admin="createuser2 os::cmd::expect_success_and_text "oc describe policybinding ':default' -n recreated-project" 'createuser2' echo "new-project: ok" -# Test running a router -os::cmd::expect_failure_and_text 'oadm router --dry-run' 'does not exist' -encoded_json='{"kind":"ServiceAccount","apiVersion":"v1","metadata":{"name":"router"}}' -os::cmd::expect_success "echo '${encoded_json}' | oc create -f - -n default" -os::cmd::expect_success "oadm policy add-scc-to-user privileged system:serviceaccount:default:router" -os::cmd::expect_success_and_text "oadm router -o yaml --credentials=${KUBECONFIG} --service-account=router -n default" 'image:.*-haproxy-router:' -os::cmd::expect_success "oadm router --credentials=${KUBECONFIG} --images='${USE_IMAGES}' --service-account=router -n default" -os::cmd::expect_success_and_text 'oadm router -n default' 'service exists' -os::cmd::expect_success_and_text 'oc get dc/router -o yaml -n default' 'readinessProbe' -os::cmd::expect_success_and_text 'oc get dc/router -o yaml -n default' 'host: localhost' - -# only when using hostnetwork should we force the probes to use localhost -os::cmd::expect_success_and_not_text "oadm router -o yaml --credentials=${KUBECONFIG} --service-account=router -n default --host-network=false" 'host: localhost' -echo "router: ok" - # Test running a registry os::cmd::expect_failure_and_text 'oadm registry --dry-run' 'does not exist' os::cmd::expect_success_and_text "oadm registry -o yaml --credentials=${KUBECONFIG}" 'image:.*-docker-registry' diff --git a/test/cmd/router.sh b/test/cmd/router.sh new file mode 100755 index 000000000000..a1c486f2bb55 --- /dev/null +++ b/test/cmd/router.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +set -o errexit +set -o nounset +set -o pipefail + +OS_ROOT=$(dirname "${BASH_SOURCE}")/../.. +source "${OS_ROOT}/hack/util.sh" +source "${OS_ROOT}/hack/cmd_util.sh" +os::log::install_errexit + +# Cleanup cluster resources created by this test +( + set +e + oadm policy remove-scc-from-user privileged -z router + oc delete sa/router -n default + exit 0 +) &>/dev/null + +defaultimage="openshift/origin-\${component}:latest" +USE_IMAGES=${USE_IMAGES:-$defaultimage} + +# Test running a router +os::cmd::expect_failure_and_text 'oadm router --dry-run' 'does not exist' +os::cmd::expect_failure_and_text 'oadm router --dry-run -o yaml' 'service account "router" is not allowed to access the host network on nodes' +os::cmd::expect_failure_and_text 'oadm router --dry-run --service-account=other -o yaml' 'service account "other" is not allowed to access the host network on nodes' +os::cmd::expect_success_and_not_text 'oadm router --dry-run --host-network=false -o yaml --credentials=${KUBECONFIG}' 'ServiceAccount' +# set ports internally +os::cmd::expect_success_and_text 'oadm router --dry-run --host-network=false -o yaml' 'containerPort: 80' +os::cmd::expect_success_and_text 'oadm router --dry-run --host-network=false --ports=80:8080 -o yaml' 'port: 8080' +os::cmd::expect_success_and_text 'oadm router --dry-run --host-network=false --ports=80,8443:443 -o yaml' 'targetPort: 8443' +os::cmd::expect_success_and_not_text 'oadm router --dry-run --host-network=false -o yaml' 'hostPort' +# don't use localhost for liveness probe by default +os::cmd::expect_success_and_not_text "oadm router --dry-run --host-network=false -o yaml" 'host: localhost' +# client env vars are optional +os::cmd::expect_success_and_not_text 'oadm router --dry-run --host-network=false -o yaml' 'OPENSHIFT_MASTER' +os::cmd::expect_success_and_not_text 'oadm router --dry-run --host-network=false --secrets-as-env -o yaml' 'OPENSHIFT_MASTER' +os::cmd::expect_success_and_text 'oadm router --dry-run --host-network=false --secrets-as-env --credentials=${KUBECONFIG} -o yaml' 'OPENSHIFT_MASTER' +# mount tls crt as secret +os::cmd::expect_success_and_not_text 'oadm router --dry-run --host-network=false -o yaml' 'value: /etc/pki/tls/private/tls.crt' +os::cmd::expect_failure_and_text "oadm router --dry-run --host-network=false --default-cert=${KUBECONFIG} -o yaml" 'the default cert must contain a private key' +os::cmd::expect_success_and_text "oadm router --dry-run --host-network=false --default-cert=images/router/haproxy-base/conf/default_pub_keys.pem -o yaml" 'value: /etc/pki/tls/private/tls.crt' +os::cmd::expect_success_and_text "oadm router --dry-run --host-network=false --default-cert=images/router/haproxy-base/conf/default_pub_keys.pem -o yaml" 'tls.key:' +os::cmd::expect_success_and_text "oadm router --dry-run --host-network=false --default-cert=images/router/haproxy-base/conf/default_pub_keys.pem -o yaml" 'tls.crt: ' +os::cmd::expect_success_and_text "oadm router --dry-run --host-network=false --default-cert=images/router/haproxy-base/conf/default_pub_keys.pem -o yaml" 'type: kubernetes.io/tls' +# upgrade the router to have access to host networks +os::cmd::expect_success "oadm policy add-scc-to-user privileged -z router" +# uses localhost for probes +os::cmd::expect_success_and_text "oadm router --dry-run -o yaml" 'host: localhost' +os::cmd::expect_failure_and_text "oadm router --ports=80,8443:443" 'container port 8443 and host port 443 must be equal' + +os::cmd::expect_success_and_text "oadm router -o yaml --credentials=${KUBECONFIG}" 'image:.*-haproxy-router:' +os::cmd::expect_success "oadm router --credentials=${KUBECONFIG} --images='${USE_IMAGES}'" +os::cmd::expect_success_and_text 'oadm router' 'service exists' +os::cmd::expect_success_and_text 'oc get dc/router -o yaml' 'readinessProbe' + +# only when using hostnetwork should we force the probes to use localhost +os::cmd::expect_success_and_not_text "oadm router -o yaml --credentials=${KUBECONFIG} --host-network=false" 'host: localhost' +echo "router: ok"