diff --git a/hack/make-p12-cert.sh b/hack/make-p12-cert.sh new file mode 100755 index 000000000000..4b632339add2 --- /dev/null +++ b/hack/make-p12-cert.sh @@ -0,0 +1,13 @@ +#!/usr/bin/bash + +CERT=${1:-} +KEY=${2:-} +P12=${3:-} +PASSWORD=${4:-} + +if [ "${CERT}" == "" ] || [ "${KEY}" == "" ] || [ "${P12}" == "" ] || [ "${PASSWORD}" == "" ]; then + echo "Usage: make-p12-cert.sh cert.crt key.key out.p12 password" + exit 1 +fi + +openssl pkcs12 -export -inkey "${KEY}" -in "${CERT}" -out "${P12}" -password "pass:${PASSWORD}" diff --git a/hack/test-cmd.sh b/hack/test-cmd.sh index 9bb5098a5cf7..a85bef076ea9 100755 --- a/hack/test-cmd.sh +++ b/hack/test-cmd.sh @@ -44,7 +44,8 @@ API_PORT=${API_PORT:-8443} API_HOST=${API_HOST:-127.0.0.1} MASTER_ADDR="${API_SCHEME}://${API_HOST}:${API_PORT}" PUBLIC_MASTER_HOST="${PUBLIC_MASTER_HOST:-${API_HOST}}" -KUBELET_SCHEME=${KUBELET_SCHEME:-http} +KUBELET_SCHEME=${KUBELET_SCHEME:-https} +KUBELET_HOST=${KUBELET_HOST:-127.0.0.1} KUBELET_PORT=${KUBELET_PORT:-10250} TEMP_DIR=${USE_TEMP:-$(mktemp -d /tmp/openshift-cmd.XXXX)} @@ -86,11 +87,34 @@ do SERVER_HOSTNAME_LIST="${SERVER_HOSTNAME_LIST},${IP_ADDRESS}" done <<< "${ALL_IP_ADDRESSES}" -openshift admin create-master-certs --overwrite=false --cert-dir="${CERT_DIR}" --hostnames="${SERVER_HOSTNAME_LIST}" --master="${MASTER_ADDR}" --public-master="${API_SCHEME}://${PUBLIC_MASTER_HOST}" -openshift admin create-node-config --listen="https://0.0.0.0:10250" --node-dir="${CERT_DIR}/node-${API_HOST}" --node="${API_HOST}" --hostnames="${SERVER_HOSTNAME_LIST}" --master="${MASTER_ADDR}" --certificate-authority="${CERT_DIR}/ca/cert.crt" --signer-cert="${CERT_DIR}/ca/cert.crt" --signer-key="${CERT_DIR}/ca/key.key" --signer-serial="${CERT_DIR}/ca/serial.txt" +openshift admin create-master-certs \ + --overwrite=false \ + --cert-dir="${CERT_DIR}" \ + --hostnames="${SERVER_HOSTNAME_LIST}" \ + --master="${MASTER_ADDR}" \ + --public-master="${API_SCHEME}://${PUBLIC_MASTER_HOST}" + +openshift admin create-node-config \ + --listen="${KUBELET_SCHEME}://0.0.0.0:${KUBELET_PORT}" \ + --node-dir="${CERT_DIR}/node-${KUBELET_HOST}" \ + --node="${KUBELET_HOST}" \ + --hostnames="${KUBELET_HOST}" \ + --master="${MASTER_ADDR}" \ + --node-client-certificate-authority="${CERT_DIR}/ca/cert.crt" \ + --certificate-authority="${CERT_DIR}/ca/cert.crt" \ + --signer-cert="${CERT_DIR}/ca/cert.crt" \ + --signer-key="${CERT_DIR}/ca/key.key" \ + --signer-serial="${CERT_DIR}/ca/serial.txt" # Start openshift -OPENSHIFT_ON_PANIC=crash openshift start --master="${API_SCHEME}://${API_HOST}:${API_PORT}" --listen="${API_SCHEME}://${API_HOST}:${API_PORT}" --hostname="${API_HOST}" --volume-dir="${VOLUME_DIR}" --cert-dir="${CERT_DIR}" --etcd-dir="${ETCD_DATA_DIR}" --create-certs=false 1>&2 & +OPENSHIFT_ON_PANIC=crash openshift start \ + --master="${API_SCHEME}://${API_HOST}:${API_PORT}" \ + --listen="${API_SCHEME}://${API_HOST}:${API_PORT}" \ + --hostname="${KUBELET_HOST}" \ + --volume-dir="${VOLUME_DIR}" \ + --cert-dir="${CERT_DIR}" \ + --etcd-dir="${ETCD_DATA_DIR}" \ + --create-certs=false 1>&2 & OS_PID=$! if [[ "${API_SCHEME}" == "https" ]]; then @@ -102,9 +126,9 @@ fi # set the home directory so we don't pick up the users .config export HOME="${CERT_DIR}/admin" -wait_for_url "http://${API_HOST}:${KUBELET_PORT}/healthz" "kubelet: " 0.25 80 +wait_for_url "${KUBELET_SCHEME}://${KUBELET_HOST}:${KUBELET_PORT}/healthz" "kubelet: " 0.25 80 wait_for_url "${API_SCHEME}://${API_HOST}:${API_PORT}/healthz" "apiserver: " 0.25 80 -wait_for_url "${API_SCHEME}://${API_HOST}:${API_PORT}/api/v1beta1/minions/127.0.0.1" "apiserver(minions): " 0.25 80 +wait_for_url "${API_SCHEME}://${API_HOST}:${API_PORT}/api/v1beta1/minions/${KUBELET_HOST}" "apiserver(minions): " 0.25 80 # profile the cli commands export OPENSHIFT_PROFILE="${CLI_PROFILE-}" diff --git a/hack/test-end-to-end.sh b/hack/test-end-to-end.sh index a3fc2f3ecfe2..01e418acf393 100755 --- a/hack/test-end-to-end.sh +++ b/hack/test-end-to-end.sh @@ -57,7 +57,8 @@ API_PORT="${API_PORT:-8443}" API_SCHEME="${API_SCHEME:-https}" MASTER_ADDR="${API_SCHEME}://${API_HOST}:${API_PORT}" PUBLIC_MASTER_HOST="${PUBLIC_MASTER_HOST:-${API_HOST}}" -KUBELET_SCHEME="${KUBELET_SCHEME:-http}" +KUBELET_SCHEME="${KUBELET_SCHEME:-https}" +KUBELET_HOST="${KUBELET_HOST:-127.0.0.1}" KUBELET_PORT="${KUBELET_PORT:-10250}" # use the docker bridge ip address until there is a good way to get the auto-selected address from master @@ -96,7 +97,10 @@ function cleanup() osc get -n test builds -o template -t '{{ range .items }}{{.metadata.name}}{{ "\n" }}{{end}}' | xargs -r -l osc build-logs -n test >"${LOG_DIR}/stibuild.log" osc get -n docker builds -o template -t '{{ range .items }}{{.metadata.name}}{{ "\n" }}{{end}}' | xargs -r -l osc build-logs -n docker >"${LOG_DIR}/dockerbuild.log" osc get -n custom builds -o template -t '{{ range .items }}{{.metadata.name}}{{ "\n" }}{{end}}' | xargs -r -l osc build-logs -n custom >"${LOG_DIR}/custombuild.log" - curl -L http://localhost:4001/v2/keys/?recursive=true > "${ARTIFACT_DIR}/etcd_dump.json" + + echo "[INFO] Dumping etcd contents to ${ARTIFACT_DIR}/etcd_dump.json" + set_curl_args 0 1 + curl ${clientcert_args} -L "${API_SCHEME}://${API_HOST}:4001/v2/keys/?recursive=true" > "${ARTIFACT_DIR}/etcd_dump.json" echo if [[ -z "${SKIP_TEARDOWN-}" ]]; then @@ -174,7 +178,6 @@ echo "[INFO] Certs dir is: ${CERT_DIR}" echo "[INFO] Using images: ${USE_IMAGES}" # Start All-in-one server and wait for health -# Specify the scheme and port for the listen address, but let the IP auto-discover. Set --public-master to localhost, for a stable link to the console. echo "[INFO] Create certificates for the OpenShift server" # find the same IP that openshift start will bind to. This allows access from pods that have to talk back to master ALL_IP_ADDRESSES=`ifconfig | grep "inet " | awk '{print $2}'` @@ -184,15 +187,37 @@ do SERVER_HOSTNAME_LIST="${SERVER_HOSTNAME_LIST},${IP_ADDRESS}" done <<< "${ALL_IP_ADDRESSES}" -openshift admin create-master-certs --overwrite=false --cert-dir="${CERT_DIR}" --hostnames="${SERVER_HOSTNAME_LIST}" --master="${MASTER_ADDR}" --public-master="${API_SCHEME}://${PUBLIC_MASTER_HOST}" -openshift admin create-node-config --listen="https://0.0.0.0:10250" --node-dir="${CERT_DIR}/node-127.0.0.1" --node="127.0.0.1" --hostnames="${SERVER_HOSTNAME_LIST}" --master="${MASTER_ADDR}" --certificate-authority="${CERT_DIR}/ca/cert.crt" --signer-cert="${CERT_DIR}/ca/cert.crt" --signer-key="${CERT_DIR}/ca/key.key" --signer-serial="${CERT_DIR}/ca/serial.txt" +openshift admin create-master-certs \ + --overwrite=false \ + --cert-dir="${CERT_DIR}" \ + --hostnames="${SERVER_HOSTNAME_LIST}" \ + --master="${MASTER_ADDR}" \ + --public-master="${API_SCHEME}://${PUBLIC_MASTER_HOST}" + +openshift admin create-node-config \ + --listen="${KUBELET_SCHEME}://0.0.0.0:${KUBELET_PORT}" \ + --node-dir="${CERT_DIR}/node-${KUBELET_HOST}" \ + --node="${KUBELET_HOST}" \ + --hostnames="${KUBELET_HOST}" \ + --master="${MASTER_ADDR}" \ + --node-client-certificate-authority="${CERT_DIR}/ca/cert.crt" \ + --certificate-authority="${CERT_DIR}/ca/cert.crt" \ + --signer-cert="${CERT_DIR}/ca/cert.crt" \ + --signer-key="${CERT_DIR}/ca/key.key" \ + --signer-serial="${CERT_DIR}/ca/serial.txt" echo "[INFO] Starting OpenShift server" sudo env "PATH=${PATH}" OPENSHIFT_PROFILE=web OPENSHIFT_ON_PANIC=crash openshift start \ - --listen="${API_SCHEME}://0.0.0.0:${API_PORT}" --master="${MASTER_ADDR}" --public-master="${API_SCHEME}://${PUBLIC_MASTER_HOST}" \ - --hostname="127.0.0.1" --volume-dir="${VOLUME_DIR}" \ - --etcd-dir="${ETCD_DATA_DIR}" --cert-dir="${CERT_DIR}" --loglevel=4 \ - --images="${USE_IMAGES}" --create-certs=false\ + --listen="${API_SCHEME}://0.0.0.0:${API_PORT}" \ + --master="${MASTER_ADDR}" \ + --public-master="${API_SCHEME}://${PUBLIC_MASTER_HOST}" \ + --hostname="${KUBELET_HOST}" \ + --volume-dir="${VOLUME_DIR}" \ + --etcd-dir="${ETCD_DATA_DIR}" \ + --cert-dir="${CERT_DIR}" \ + --loglevel=4 \ + --images="${USE_IMAGES}" \ + --create-certs=false \ &> "${LOG_DIR}/openshift.log" & OS_PID=$! @@ -208,9 +233,9 @@ if [[ "${API_SCHEME}" == "https" ]]; then echo "[INFO] To debug: export OPENSHIFTCONFIG=$OPENSHIFTCONFIG" fi -wait_for_url "${KUBELET_SCHEME}://127.0.0.1:${KUBELET_PORT}/healthz" "kubelet: " 0.5 60 +wait_for_url "${KUBELET_SCHEME}://${KUBELET_HOST}:${KUBELET_PORT}/healthz" "[INFO] kubelet: " 0.5 60 wait_for_url "${API_SCHEME}://${API_HOST}:${API_PORT}/healthz" "apiserver: " 0.25 80 -wait_for_url "${API_SCHEME}://${API_HOST}:${API_PORT}/api/v1beta1/minions/127.0.0.1" "apiserver(minions): " 0.25 80 +wait_for_url "${API_SCHEME}://${API_HOST}:${API_PORT}/api/v1beta1/minions/${KUBELET_HOST}" "apiserver(minions): " 0.25 80 # add e2e-user as a viewer for the default namespace so we can see infrastructure pieces appear openshift ex policy add-role-to-user view e2e-user --namespace=default diff --git a/hack/test-extended.sh b/hack/test-extended.sh index 8e06dc2db109..6c710b64db74 100755 --- a/hack/test-extended.sh +++ b/hack/test-extended.sh @@ -56,23 +56,24 @@ start_server() { echo "[INFO] Create certificates for the OpenShift master" env "PATH=${PATH}" openshift admin create-master-certs \ - --overwrite=false \ - --cert-dir="${CERT_DIR}" \ - --hostnames="${SERVER_HOSTNAME_LIST}" \ - --master="https://${OS_MASTER_ADDR}" \ - --public-master="https://${OS_MASTER_ADDR}" + --overwrite=false \ + --cert-dir="${CERT_DIR}" \ + --hostnames="${SERVER_HOSTNAME_LIST}" \ + --master="https://${OS_MASTER_ADDR}" \ + --public-master="https://${OS_MASTER_ADDR}" echo "[INFO] Create certificates for the OpenShift node" env "PATH=${PATH}" openshift admin create-node-config \ - --listen="https://0.0.0.0:10250" \ - --node-dir="${CERT_DIR}/node-127.0.0.1" \ - --node="127.0.0.1" \ - --hostnames="${SERVER_HOSTNAME_LIST}" \ - --master="https://${OS_MASTER_ADDR}" \ - --certificate-authority="${CERT_DIR}/ca/cert.crt" \ - --signer-cert="${CERT_DIR}/ca/cert.crt" \ - --signer-key="${CERT_DIR}/ca/key.key" \ - --signer-serial="${CERT_DIR}/ca/serial.txt" + --listen="https://0.0.0.0:10250" \ + --node-dir="${CERT_DIR}/node-127.0.0.1" \ + --node="127.0.0.1" \ + --hostnames="${SERVER_HOSTNAME_LIST}" \ + --master="https://${OS_MASTER_ADDR}" \ + --node-client-certificate-authority="${CERT_DIR}/ca/cert.crt" \ + --certificate-authority="${CERT_DIR}/ca/cert.crt" \ + --signer-cert="${CERT_DIR}/ca/cert.crt" \ + --signer-key="${CERT_DIR}/ca/key.key" \ + --signer-serial="${CERT_DIR}/ca/serial.txt" echo "[INFO] Starting OpenShift server" sudo env "PATH=${PATH}" openshift start \ diff --git a/pkg/build/registry/buildlog/rest.go b/pkg/build/registry/buildlog/rest.go index 0f6080864579..68794587015b 100644 --- a/pkg/build/registry/buildlog/rest.go +++ b/pkg/build/registry/buildlog/rest.go @@ -2,10 +2,13 @@ package buildlog import ( "fmt" + "net" "net/http" "net/url" + "strconv" kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest" kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" @@ -13,13 +16,13 @@ import ( "github.com/openshift/origin/pkg/build/api" "github.com/openshift/origin/pkg/build/registry/build" buildutil "github.com/openshift/origin/pkg/build/util" - "github.com/openshift/origin/pkg/cmd/server/kubernetes" ) // REST is an implementation of RESTStorage for the api server. type REST struct { - BuildRegistry build.Registry - PodControl PodControlInterface + BuildRegistry build.Registry + PodControl PodControlInterface + ConnectionInfo kclient.ConnectionInfoGetter } type PodControlInterface interface { @@ -37,13 +40,16 @@ func (r RealPodControl) getPod(namespace, name string) (*kapi.Pod, error) { // NewREST creates a new REST for BuildLog // Takes build registry and pod client to get necessary attributes to assemble // URL to which the request shall be redirected in order to get build logs. -func NewREST(b build.Registry, pn kclient.PodsNamespacer) *REST { +func NewREST(b build.Registry, pn kclient.PodsNamespacer, connectionInfo kclient.ConnectionInfoGetter) *REST { return &REST{ - BuildRegistry: b, - PodControl: RealPodControl{pn}, + BuildRegistry: b, + PodControl: RealPodControl{pn}, + ConnectionInfo: connectionInfo, } } +var _ = rest.Redirector(&REST{}) + // Redirector implementation func (r *REST) ResourceLocation(ctx kapi.Context, id string) (*url.URL, http.RoundTripper, error) { build, err := r.BuildRegistry.GetBuild(ctx, id) @@ -63,7 +69,17 @@ func (r *REST) ResourceLocation(ctx kapi.Context, id string) (*url.URL, http.Rou buildPodNamespace := pod.Namespace // Build will take place only in one container buildContainerName := pod.Spec.Containers[0].Name - location := fmt.Sprintf("%s:%d/containerLogs/%s/%s/%s", buildPodHost, kubernetes.NodePort, buildPodNamespace, buildPodName, buildContainerName) + + scheme, port, transport, err := r.ConnectionInfo.GetConnectionInfo(buildPodHost) + if err != nil { + return nil, nil, err + } + + location := &url.URL{ + Scheme: scheme, + Host: net.JoinHostPort(buildPodHost, strconv.FormatUint(uint64(port), 10)), + Path: fmt.Sprintf("/containerLogs/%s/%s/%s", buildPodNamespace, buildPodName, buildContainerName), + } // Pod in which build take place can't be in the Pending or Unknown phase, // cause no containers are present in the Pod in those phases. @@ -73,16 +89,14 @@ func (r *REST) ResourceLocation(ctx kapi.Context, id string) (*url.URL, http.Rou switch build.Status { case api.BuildStatusRunning: - location += "?follow=1" + location.RawQuery = "follow=1" case api.BuildStatusComplete, api.BuildStatusFailed: // Do not follow the Complete and Failed logs as the streaming already finished. default: return nil, nil, fielderrors.NewFieldInvalid("build.Status", build.Status, "must be Running, Complete or Failed") } - return &url.URL{ - Host: location, - }, nil, nil + return location, transport, nil } func (r *REST) New() runtime.Object { diff --git a/pkg/build/registry/buildlog/rest_test.go b/pkg/build/registry/buildlog/rest_test.go index 803f32c65458..af3af0c6b101 100644 --- a/pkg/build/registry/buildlog/rest_test.go +++ b/pkg/build/registry/buildlog/rest_test.go @@ -2,14 +2,15 @@ package buildlog import ( "fmt" + "net/http" "testing" kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest" + kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/openshift/origin/pkg/build/api" "github.com/openshift/origin/pkg/build/registry/test" - "github.com/openshift/origin/pkg/cmd/server/kubernetes" ) type podControl struct{} @@ -37,12 +38,9 @@ func (p *podControl) getPod(namespace, podName string) (*kapi.Pod, error) { // is evaluating the outcome based only on build state. func TestRegistryResourceLocation(t *testing.T) { expectedLocations := map[api.BuildStatus]string{ - api.BuildStatusComplete: fmt.Sprintf("//foo-host:%d/containerLogs/%s/running/foo-container", - kubernetes.NodePort, kapi.NamespaceDefault), - api.BuildStatusFailed: fmt.Sprintf("//foo-host:%d/containerLogs/%s/running/foo-container", - kubernetes.NodePort, kapi.NamespaceDefault), - api.BuildStatusRunning: fmt.Sprintf("//foo-host:%d/containerLogs/%s/running/foo-container?follow=1", - kubernetes.NodePort, kapi.NamespaceDefault), + api.BuildStatusComplete: fmt.Sprintf("https://foo-host:12345/containerLogs/%s/running/foo-container", kapi.NamespaceDefault), + api.BuildStatusFailed: fmt.Sprintf("https://foo-host:12345/containerLogs/%s/running/foo-container", kapi.NamespaceDefault), + api.BuildStatusRunning: fmt.Sprintf("https://foo-host:12345/containerLogs/%s/running/foo-container?follow=1", kapi.NamespaceDefault), api.BuildStatusNew: "", api.BuildStatusPending: "", api.BuildStatusError: "", @@ -103,15 +101,14 @@ func TestRegistryResourceLocationPodPhases(t *testing.T) { func resourceLocationHelper(buildStatus api.BuildStatus, podPhase string, ctx kapi.Context) (string, error) { expectedBuild := mockBuild(buildStatus, podPhase) buildRegistry := test.BuildRegistry{Build: expectedBuild} - storage := REST{&buildRegistry, &podControl{}} + + storage := REST{&buildRegistry, &podControl{}, &kclient.HTTPKubeletClient{EnableHttps: true, Port: 12345, Client: &http.Client{}}} redirector := rest.Redirector(&storage) location, _, err := redirector.ResourceLocation(ctx, "foo-build") - - if location != nil { - return location.String(), err + if err != nil { + return "", err } - - return "", err + return location.String(), err } func mockPod(podPhase kapi.PodPhase) *kapi.Pod { diff --git a/pkg/client/buildlogs.go b/pkg/client/buildlogs.go index a7952bf29977..b97d7891af2e 100644 --- a/pkg/client/buildlogs.go +++ b/pkg/client/buildlogs.go @@ -11,7 +11,7 @@ type BuildLogsNamespacer interface { // BuildLogsInterface exposes methods on BuildLogs resources. type BuildLogsInterface interface { - Redirect(name string) *kclient.Request + Get(name string) *kclient.Request } // buildLogs implements BuildLogsNamespacer interface @@ -28,7 +28,7 @@ func newBuildLogs(c *Client, namespace string) *buildLogs { } } -// Redirect builds and returns a buildLog request -func (c *buildLogs) Redirect(name string) *kclient.Request { - return c.r.Get().Namespace(c.ns).Prefix("redirect").Resource("buildLogs").Name(name) +// Get builds and returns a buildLog request +func (c *buildLogs) Get(name string) *kclient.Request { + return c.r.Get().Namespace(c.ns).Prefix("proxy").Resource("buildLogs").Name(name) } diff --git a/pkg/client/fake_buildlogs.go b/pkg/client/fake_buildlogs.go index efc1474b6912..b964e58e0a41 100644 --- a/pkg/client/fake_buildlogs.go +++ b/pkg/client/fake_buildlogs.go @@ -11,8 +11,8 @@ type FakeBuildLogs struct { Namespace string } -// Redirect builds and returns a buildLog request -func (c *FakeBuildLogs) Redirect(name string) *kclient.Request { - c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "redirect"}) +// Get builds and returns a buildLog request +func (c *FakeBuildLogs) Get(name string) *kclient.Request { + c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "proxy"}) return &kclient.Request{} } diff --git a/pkg/cmd/cli/cmd/buildlogs.go b/pkg/cmd/cli/cmd/buildlogs.go index 0d334509f30e..d198158cf10a 100644 --- a/pkg/cmd/cli/cmd/buildlogs.go +++ b/pkg/cmd/cli/cmd/buildlogs.go @@ -35,9 +35,7 @@ func NewCmdBuildLogs(fullName string, f *clientcmd.Factory, out io.Writer) *cobr c, _, err := f.Clients() checkErr(err) - request := c.BuildLogs(namespace).Redirect(args[0]) - - readCloser, err := request.Stream() + readCloser, err := c.BuildLogs(namespace).Get(args[0]).Stream() checkErr(err) defer readCloser.Close() diff --git a/pkg/cmd/cli/cmd/cancelbuild.go b/pkg/cmd/cli/cmd/cancelbuild.go index 44252b72915a..8b0e3b96f0d4 100644 --- a/pkg/cmd/cli/cmd/cancelbuild.go +++ b/pkg/cmd/cli/cmd/cancelbuild.go @@ -65,7 +65,7 @@ func NewCmdCancelBuild(fullName string, f *clientcmd.Factory, out io.Writer) *co glog.V(2).Infof("Build %v has not yet generated any logs.", buildName) } else { - response, err := client.BuildLogs(namespace).Redirect(buildName).Do().Raw() + response, err := client.BuildLogs(namespace).Get(buildName).Do().Raw() if err != nil { glog.Errorf("Could not fetch build logs for %s: %v", buildName, err) } else { diff --git a/pkg/cmd/cli/cmd/startbuild.go b/pkg/cmd/cli/cmd/startbuild.go index 44dc39f26283..a1174bab2cd5 100644 --- a/pkg/cmd/cli/cmd/startbuild.go +++ b/pkg/cmd/cli/cmd/startbuild.go @@ -85,7 +85,7 @@ func NewCmdStartBuild(fullName string, f *clientcmd.Factory, out io.Writer) *cob if build.Name == newBuild.Name { switch build.Status { case buildapi.BuildStatusRunning, buildapi.BuildStatusComplete, buildapi.BuildStatusFailed: - rd, err := client.BuildLogs(namespace).Redirect(newBuild.Name).Stream() + rd, err := client.BuildLogs(namespace).Get(newBuild.Name).Stream() checkErr(err) defer rd.Close() diff --git a/pkg/cmd/server/admin/create_mastercerts.go b/pkg/cmd/server/admin/create_mastercerts.go index 82d685de2e04..c90578978b15 100644 --- a/pkg/cmd/server/admin/create_mastercerts.go +++ b/pkg/cmd/server/admin/create_mastercerts.go @@ -103,21 +103,28 @@ func (o CreateMasterCertsOptions) CreateMasterCerts() error { SerialFile: DefaultSerialFilename(o.CertDir, DefaultCADir), } - for _, clientCertInfo := range DefaultClientCerts(o.CertDir) { - clientCertOptions := CreateClientCertOptions{ - GetSignerCertOptions: &getSignerCertOptions, + if err := o.createServerCerts(&getSignerCertOptions); err != nil { + return err + } - CertFile: clientCertInfo.CertLocation.CertFile, - KeyFile: clientCertInfo.CertLocation.KeyFile, + if err := o.createAPIClients(&getSignerCertOptions); err != nil { + return err + } - User: clientCertInfo.User, - Groups: util.StringList(clientCertInfo.Groups.List()), - Overwrite: o.Overwrite, - } - if err := clientCertOptions.Validate(nil); err != nil { - return err - } - if _, err := clientCertOptions.CreateClientCert(); err != nil { + if err := o.createEtcdClientCerts(&getSignerCertOptions); err != nil { + return err + } + + if err := o.createKubeletClientCerts(&getSignerCertOptions); err != nil { + return err + } + + return nil +} + +func (o CreateMasterCertsOptions) createAPIClients(getSignerCertOptions *GetSignerCertOptions) error { + for _, clientCertInfo := range DefaultAPIClientCerts(o.CertDir) { + if err := o.createClientCert(clientCertInfo, getSignerCertOptions); err != nil { return err } @@ -129,9 +136,9 @@ func (o CreateMasterCertsOptions) CreateMasterCerts() error { CertFile: clientCertInfo.CertLocation.CertFile, KeyFile: clientCertInfo.CertLocation.KeyFile, - UserNick: clientCertInfo.SubDir, + UserNick: clientCertInfo.User, - KubeConfigFile: path.Join(filepath.Dir(clientCertOptions.CertFile), ".kubeconfig"), + KubeConfigFile: path.Join(filepath.Dir(clientCertInfo.CertLocation.CertFile), ".kubeconfig"), } if err := createKubeConfigOptions.Validate(nil); err != nil { return err @@ -140,10 +147,48 @@ func (o CreateMasterCertsOptions) CreateMasterCerts() error { return err } } + return nil +} +func (o CreateMasterCertsOptions) createEtcdClientCerts(getSignerCertOptions *GetSignerCertOptions) error { + for _, clientCertInfo := range DefaultEtcdClientCerts(o.CertDir) { + if err := o.createClientCert(clientCertInfo, getSignerCertOptions); err != nil { + return err + } + } + return nil +} + +func (o CreateMasterCertsOptions) createKubeletClientCerts(getSignerCertOptions *GetSignerCertOptions) error { + for _, clientCertInfo := range DefaultKubeletClientCerts(o.CertDir) { + if err := o.createClientCert(clientCertInfo, getSignerCertOptions); err != nil { + return err + } + } + return nil +} + +func (o CreateMasterCertsOptions) createClientCert(clientCertInfo ClientCertInfo, getSignerCertOptions *GetSignerCertOptions) error { + clientCertOptions := CreateClientCertOptions{ + GetSignerCertOptions: getSignerCertOptions, + + CertFile: clientCertInfo.CertLocation.CertFile, + KeyFile: clientCertInfo.CertLocation.KeyFile, + + User: clientCertInfo.User, + Groups: util.StringList(clientCertInfo.Groups.List()), + Overwrite: o.Overwrite, + } + if _, err := clientCertOptions.CreateClientCert(); err != nil { + return err + } + return nil +} + +func (o CreateMasterCertsOptions) createServerCerts(getSignerCertOptions *GetSignerCertOptions) error { for _, serverCertInfo := range DefaultServerCerts(o.CertDir) { serverCertOptions := CreateServerCertOptions{ - GetSignerCertOptions: &getSignerCertOptions, + GetSignerCertOptions: getSignerCertOptions, CertFile: serverCertInfo.CertFile, KeyFile: serverCertInfo.KeyFile, @@ -151,7 +196,6 @@ func (o CreateMasterCertsOptions) CreateMasterCerts() error { Hostnames: o.Hostnames, Overwrite: o.Overwrite, } - if err := serverCertOptions.Validate(nil); err != nil { return err } @@ -159,6 +203,5 @@ func (o CreateMasterCertsOptions) CreateMasterCerts() error { return err } } - return nil } diff --git a/pkg/cmd/server/admin/create_nodeconfig.go b/pkg/cmd/server/admin/create_nodeconfig.go index c213e6c0db09..927a263ea857 100644 --- a/pkg/cmd/server/admin/create_nodeconfig.go +++ b/pkg/cmd/server/admin/create_nodeconfig.go @@ -42,12 +42,13 @@ type CreateNodeConfigOptions struct { DNSIP string ListenAddr flagtypes.Addr - ClientCertFile string - ClientKeyFile string - ServerCertFile string - ServerKeyFile string - APIServerCAFile string - APIServerURL string + ClientCertFile string + ClientKeyFile string + ServerCertFile string + ServerKeyFile string + NodeClientCAFile string + APIServerCAFile string + APIServerURL string } func NewCommandNodeConfig(commandName string, fullName string, out io.Writer) *cobra.Command { @@ -85,10 +86,11 @@ func NewCommandNodeConfig(commandName string, fullName string, out io.Writer) *c flags.StringVar(&options.DNSIP, "dns-ip", options.DNSIP, "DNS server IP for the cluster.") flags.Var(&options.ListenAddr, "listen", "The address to listen for connections on (scheme://host:port).") - flags.StringVar(&options.ClientCertFile, "client-certificate", "", "The client cert file.") - flags.StringVar(&options.ClientKeyFile, "client-key", "", "The client key file.") - flags.StringVar(&options.ServerCertFile, "server-certificate", "", "The server cert file for serving secure traffic.") - flags.StringVar(&options.ServerKeyFile, "server-key", "", "The server key file for serving secure traffic.") + flags.StringVar(&options.ClientCertFile, "client-certificate", "", "The client cert file for the node to contact the API.") + flags.StringVar(&options.ClientKeyFile, "client-key", "", "The client key file for the node to contact the API.") + flags.StringVar(&options.ServerCertFile, "server-certificate", "", "The server cert file for the node to serve secure traffic.") + flags.StringVar(&options.ServerKeyFile, "server-key", "", "The server key file for the node to serve secure traffic.") + flags.StringVar(&options.NodeClientCAFile, "node-client-certificate-authority", options.NodeClientCAFile, "The file containing signing authorities to use to verify requests to the node. If empty, all requests will be allowed.") flags.StringVar(&options.APIServerURL, "master", options.APIServerURL, "The API server's URL.") flags.StringVar(&options.APIServerCAFile, "certificate-authority", options.APIServerCAFile, "Path to the API server's CA file.") @@ -101,11 +103,12 @@ func NewDefaultCreateNodeConfigOptions() *CreateNodeConfigOptions { options.DNSDomain = "local" options.APIServerURL = "https://localhost:8443" options.APIServerCAFile = "openshift.local.certificates/ca/cert.crt" + options.NodeClientCAFile = "openshift.local.certificates/ca/cert.crt" imageTemplate := variable.NewDefaultImageTemplate() options.NetworkContainerImage = imageTemplate.ExpandOrDie("pod") - options.ListenAddr = flagtypes.Addr{Value: "0.0.0.0:10250", DefaultScheme: "http", DefaultPort: 10250, AllowPrefix: true}.Default() + options.ListenAddr = flagtypes.Addr{Value: "0.0.0.0:10250", DefaultScheme: "https", DefaultPort: 10250, AllowPrefix: true}.Default() return options } @@ -122,6 +125,10 @@ func (o CreateNodeConfigOptions) UseTLS() bool { return o.ListenAddr.URL.Scheme == "https" } +func (o CreateNodeConfigOptions) UseNodeClientCA() bool { + return o.UseTLS() && len(o.NodeClientCAFile) > 0 +} + func (o CreateNodeConfigOptions) Validate(args []string) error { if len(args) != 0 { return errors.New("no arguments are supported") @@ -187,9 +194,12 @@ func CopyFile(src, dest string, permissions os.FileMode) error { func (o CreateNodeConfigOptions) CreateNodeFolder() error { clientCertFile := path.Join(o.NodeConfigDir, "client.crt") clientKeyFile := path.Join(o.NodeConfigDir, "client.key") + apiServerCAFile := path.Join(o.NodeConfigDir, "ca.crt") + serverCertFile := path.Join(o.NodeConfigDir, "server.crt") serverKeyFile := path.Join(o.NodeConfigDir, "server.key") - clientCopyOfCAFile := path.Join(o.NodeConfigDir, "ca.crt") + nodeClientCAFile := path.Join(o.NodeConfigDir, "node-client-ca.crt") + kubeConfigFile := path.Join(o.NodeConfigDir, ".kubeconfig") nodeConfigFile := path.Join(o.NodeConfigDir, "node-config.yaml") nodeJSONFile := path.Join(o.NodeConfigDir, "node-registration.json") @@ -201,14 +211,19 @@ func (o CreateNodeConfigOptions) CreateNodeFolder() error { if err := o.MakeServerCert(serverCertFile, serverKeyFile); err != nil { return err } + if o.UseNodeClientCA() { + if err := o.MakeNodeClientCA(nodeClientCAFile); err != nil { + return err + } + } } - if err := o.MakeCA(clientCopyOfCAFile); err != nil { + if err := o.MakeAPIServerCA(apiServerCAFile); err != nil { return err } - if err := o.MakeKubeConfig(clientCertFile, clientKeyFile, clientCopyOfCAFile, kubeConfigFile); err != nil { + if err := o.MakeKubeConfig(clientCertFile, clientKeyFile, apiServerCAFile, kubeConfigFile); err != nil { return err } - if err := o.MakeNodeConfig(serverCertFile, serverKeyFile, kubeConfigFile, nodeConfigFile); err != nil { + if err := o.MakeNodeConfig(serverCertFile, serverKeyFile, nodeClientCAFile, kubeConfigFile, nodeConfigFile); err != nil { return err } if err := o.MakeNodeJSON(nodeJSONFile); err != nil { @@ -279,7 +294,7 @@ func (o CreateNodeConfigOptions) MakeServerCert(serverCertFile, serverKeyFile st return nil } -func (o CreateNodeConfigOptions) MakeCA(clientCopyOfCAFile string) error { +func (o CreateNodeConfigOptions) MakeAPIServerCA(clientCopyOfCAFile string) error { if err := CopyFile(o.APIServerCAFile, clientCopyOfCAFile, 0644); err != nil { return err } @@ -287,6 +302,14 @@ func (o CreateNodeConfigOptions) MakeCA(clientCopyOfCAFile string) error { return nil } +func (o CreateNodeConfigOptions) MakeNodeClientCA(clientCopyOfCAFile string) error { + if err := CopyFile(o.NodeClientCAFile, clientCopyOfCAFile, 0644); err != nil { + return err + } + + return nil +} + func (o CreateNodeConfigOptions) MakeKubeConfig(clientCertFile, clientKeyFile, clientCopyOfCAFile, kubeConfigFile string) error { createKubeConfigOptions := CreateKubeConfigOptions{ APIServerURL: o.APIServerURL, @@ -309,7 +332,7 @@ func (o CreateNodeConfigOptions) MakeKubeConfig(clientCertFile, clientKeyFile, c return nil } -func (o CreateNodeConfigOptions) MakeNodeConfig(serverCertFile, serverKeyFile, kubeConfigFile, nodeConfigFile string) error { +func (o CreateNodeConfigOptions) MakeNodeConfig(serverCertFile, serverKeyFile, nodeClientCAFile, kubeConfigFile, nodeConfigFile string) error { config := &configapi.NodeConfig{ NodeName: o.NodeName, @@ -332,6 +355,7 @@ func (o CreateNodeConfigOptions) MakeNodeConfig(serverCertFile, serverKeyFile, k CertFile: serverCertFile, KeyFile: serverKeyFile, } + config.ServingInfo.ClientCA = nodeClientCAFile } // Resolve relative to CWD diff --git a/pkg/cmd/server/admin/create_nodeconfig_test.go b/pkg/cmd/server/admin/create_nodeconfig_test.go index 11792d8e7885..a0a7e3ecade0 100644 --- a/pkg/cmd/server/admin/create_nodeconfig_test.go +++ b/pkg/cmd/server/admin/create_nodeconfig_test.go @@ -42,7 +42,7 @@ func TestNodeConfigTLS(t *testing.T) { defer os.Remove(signerKey) defer os.Remove(signerSerial) - configDirName := executeNodeConfig([]string{"--node=my-node", "--hostnames=example.org", "--listen=https://0.0.0.0", "--certificate-authority=" + signerCert, "--signer-cert=" + signerCert, "--signer-key=" + signerKey, "--signer-serial=" + signerSerial}) + configDirName := executeNodeConfig([]string{"--node=my-node", "--hostnames=example.org", "--listen=https://0.0.0.0", "--certificate-authority=" + signerCert, "--node-client-certificate-authority=" + signerCert, "--signer-cert=" + signerCert, "--signer-key=" + signerKey, "--signer-serial=" + signerSerial}) defer os.Remove(configDirName) configDir, err := os.Open(configDirName) @@ -56,7 +56,7 @@ func TestNodeConfigTLS(t *testing.T) { } filenames := util.NewStringSet(fileNameSlice...) - expectedNames := util.NewStringSet("client.crt", "client.key", "server.crt", "server.key", ".kubeconfig", "node-config.yaml", "node-registration.json", "ca.crt") + expectedNames := util.NewStringSet("client.crt", "client.key", "server.crt", "server.key", "node-client-ca.crt", ".kubeconfig", "node-config.yaml", "node-registration.json", "ca.crt") if !filenames.HasAll(expectedNames.List()...) || !expectedNames.HasAll(filenames.List()...) { t.Errorf("expected %v, got %v", expectedNames.List(), filenames.List()) } diff --git a/pkg/cmd/server/admin/default_certs.go b/pkg/cmd/server/admin/default_certs.go index 852cdfce0de2..87661542fa62 100644 --- a/pkg/cmd/server/admin/default_certs.go +++ b/pkg/cmd/server/admin/default_certs.go @@ -12,7 +12,8 @@ import ( ) const ( - DefaultCADir = "ca" + DefaultCADir = "ca" + DefaultMasterDir = "master" ) type ClientCertInfo struct { @@ -30,7 +31,51 @@ func DefaultRootCAFile(certDir string) string { return DefaultCertFilename(certDir, DefaultCADir) } -func DefaultClientCerts(certDir string) []ClientCertInfo { +func DefaultKubeletClientCAFile(certDir string) string { + return DefaultRootCAFile(certDir) +} + +func DefaultKubeletClientCerts(certDir string) []ClientCertInfo { + return []ClientCertInfo{ + DefaultMasterKubeletClientCertInfo(certDir), + } +} + +func DefaultMasterKubeletClientCertInfo(certDir string) ClientCertInfo { + return ClientCertInfo{ + CertLocation: configapi.CertInfo{ + CertFile: path.Join(certDir, DefaultMasterDir, "kubelet-client.crt"), + KeyFile: path.Join(certDir, DefaultMasterDir, "kubelet-client.key"), + }, + User: "system:master", + } +} + +func DefaultEtcdClientCAFile(certDir string) string { + return DefaultRootCAFile(certDir) +} + +func DefaultEtcdClientCerts(certDir string) []ClientCertInfo { + return []ClientCertInfo{ + DefaultMasterEtcdClientCertInfo(certDir), + } +} + +func DefaultMasterEtcdClientCertInfo(certDir string) ClientCertInfo { + return ClientCertInfo{ + CertLocation: configapi.CertInfo{ + CertFile: path.Join(certDir, DefaultMasterDir, "etcd-client.crt"), + KeyFile: path.Join(certDir, DefaultMasterDir, "etcd-client.key"), + }, + User: "system:master", + } +} + +func DefaultAPIClientCAFile(certDir string) string { + return DefaultRootCAFile(certDir) +} + +func DefaultAPIClientCerts(certDir string) []ClientCertInfo { return []ClientCertInfo{ DefaultDeployerClientCertInfo(certDir), DefaultOpenshiftLoopbackClientCertInfo(certDir), @@ -115,13 +160,26 @@ func DefaultServerCerts(certDir string) []configapi.CertInfo { return []configapi.CertInfo{ DefaultMasterServingCertInfo(certDir), DefaultAssetServingCertInfo(certDir), + DefaultEtcdServingCertInfo(certDir), } } func DefaultMasterServingCertInfo(certDir string) configapi.CertInfo { return configapi.CertInfo{ - CertFile: DefaultCertFilename(certDir, "master"), - KeyFile: DefaultKeyFilename(certDir, "master"), + CertFile: path.Join(certDir, DefaultMasterDir, "server.crt"), + KeyFile: path.Join(certDir, DefaultMasterDir, "server.key"), + } +} + +func DefaultAssetServingCertInfo(certDir string) configapi.CertInfo { + // Use master certs for assets also + return DefaultMasterServingCertInfo(certDir) +} + +func DefaultEtcdServingCertInfo(certDir string) configapi.CertInfo { + return configapi.CertInfo{ + CertFile: path.Join(certDir, "etcd", "server.crt"), + KeyFile: path.Join(certDir, "etcd", "server.key"), } } @@ -137,21 +195,14 @@ func DefaultNodeServingCertInfo(certDir, nodeName string) configapi.CertInfo { } func DefaultNodeClientCertInfo(certDir, nodeName string) configapi.CertInfo { return configapi.CertInfo{ - CertFile: path.Join(certDir, DefaultNodeDir(nodeName), "client.crt"), - KeyFile: path.Join(certDir, DefaultNodeDir(nodeName), "client.key"), + CertFile: path.Join(certDir, DefaultNodeDir(nodeName), "master-client.crt"), + KeyFile: path.Join(certDir, DefaultNodeDir(nodeName), "master-client.key"), } } func DefaultNodeKubeConfigFile(certDir, nodeName string) string { return path.Join(certDir, DefaultNodeDir(nodeName), ".kubeconfig") } -func DefaultAssetServingCertInfo(certDir string) configapi.CertInfo { - return configapi.CertInfo{ - CertFile: DefaultCertFilename(certDir, "master"), - KeyFile: DefaultKeyFilename(certDir, "master"), - } -} - func DefaultCertDir(certDir, username string) string { return path.Join(certDir, username) } diff --git a/pkg/cmd/server/admin/overwrite_bootstrappolicy.go b/pkg/cmd/server/admin/overwrite_bootstrappolicy.go index 4c1d3fccebbe..93439fd5f0e2 100644 --- a/pkg/cmd/server/admin/overwrite_bootstrappolicy.go +++ b/pkg/cmd/server/admin/overwrite_bootstrappolicy.go @@ -92,7 +92,7 @@ func (o OverwriteBootstrapPolicyOptions) OverwriteBootstrapPolicy() error { return utilerrors.NewAggregate(err) } - etcdHelper, err := etcd.NewOpenShiftEtcdHelper(masterConfig.EtcdClientInfo.URL) + etcdHelper, err := etcd.NewOpenShiftEtcdHelper(masterConfig.EtcdClientInfo) if err != nil { return err } diff --git a/pkg/cmd/server/api/helpers.go b/pkg/cmd/server/api/helpers.go index 1f762b0dad9c..d5e2dcfac548 100644 --- a/pkg/cmd/server/api/helpers.go +++ b/pkg/cmd/server/api/helpers.go @@ -3,7 +3,6 @@ package api import ( "crypto/x509" "fmt" - "io/ioutil" kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd" @@ -33,10 +32,19 @@ func GetMasterFileReferences(config *MasterConfig) []*string { refs = append(refs, &config.EtcdClientInfo.ClientCert.KeyFile) refs = append(refs, &config.EtcdClientInfo.CA) + refs = append(refs, &config.KubeletClientInfo.ClientCert.CertFile) + refs = append(refs, &config.KubeletClientInfo.ClientCert.KeyFile) + refs = append(refs, &config.KubeletClientInfo.CA) + if config.EtcdConfig != nil { refs = append(refs, &config.EtcdConfig.ServingInfo.ServerCert.CertFile) refs = append(refs, &config.EtcdConfig.ServingInfo.ServerCert.KeyFile) refs = append(refs, &config.EtcdConfig.ServingInfo.ClientCA) + + refs = append(refs, &config.EtcdConfig.PeerServingInfo.ServerCert.CertFile) + refs = append(refs, &config.EtcdConfig.PeerServingInfo.ServerCert.KeyFile) + refs = append(refs, &config.EtcdConfig.PeerServingInfo.ClientCA) + refs = append(refs, &config.EtcdConfig.StorageDir) } @@ -139,15 +147,7 @@ func UseTLS(servingInfo ServingInfo) bool { // GetAPIClientCertCAPool returns the cert pool used to validate client certificates to the API server func GetAPIClientCertCAPool(options MasterConfig) (*x509.CertPool, error) { - certs, err := getAPIClientCertCAs(options) - if err != nil { - return nil, err - } - roots := x509.NewCertPool() - for _, root := range certs { - roots.AddCert(root) - } - return roots, nil + return crypto.CertPoolFromFile(options.ServingInfo.ClientCA) } // GetClientCertCAPool returns a cert pool containing all client CAs that could be presented (union of API and OAuth) @@ -181,15 +181,7 @@ func GetAPIServerCertCAPool(options MasterConfig) (*x509.CertPool, error) { return x509.NewCertPool(), nil } - caRoots, err := crypto.GetTLSCARoots(options.ServingInfo.ClientCA) - if err != nil { - return nil, err - } - roots := x509.NewCertPool() - for _, root := range caRoots.Roots { - roots.AddCert(root) - } - return roots, nil + return crypto.CertPoolFromFile(options.ServingInfo.ClientCA) } func getOAuthClientCertCAs(options MasterConfig) ([]*x509.Certificate, error) { @@ -206,13 +198,9 @@ func getOAuthClientCertCAs(options MasterConfig) ([]*x509.Certificate, error) { case (*RequestHeaderIdentityProvider): caFile := provider.ClientCA if len(caFile) == 0 { - return nil, nil + continue } - caPEMBlock, err := ioutil.ReadFile(caFile) - if err != nil { - return nil, err - } - certs, err := crypto.CertsFromPEM(caPEMBlock) + certs, err := crypto.CertificatesFromFile(caFile) if err != nil { return nil, fmt.Errorf("Error reading %s: %s", caFile, err) } @@ -229,12 +217,26 @@ func getAPIClientCertCAs(options MasterConfig) ([]*x509.Certificate, error) { return nil, nil } - apiClientCertCAs, err := crypto.GetTLSCARoots(options.ServingInfo.ClientCA) - if err != nil { - return nil, err + return crypto.CertificatesFromFile(options.ServingInfo.ClientCA) +} + +func GetKubeletClientConfig(options MasterConfig) *kclient.KubeletConfig { + config := &kclient.KubeletConfig{ + Port: options.KubeletClientInfo.Port, + } + + if len(options.KubeletClientInfo.CA) > 0 { + config.EnableHttps = true + config.CAFile = options.KubeletClientInfo.CA + } + + if len(options.KubeletClientInfo.ClientCert.CertFile) > 0 { + config.EnableHttps = true + config.CertFile = options.KubeletClientInfo.ClientCert.CertFile + config.KeyFile = options.KubeletClientInfo.ClientCert.KeyFile } - return apiClientCertCAs.Roots, nil + return config } func IsPasswordAuthenticator(provider IdentityProvider) bool { diff --git a/pkg/cmd/server/api/types.go b/pkg/cmd/server/api/types.go index 7a706935d236..c8817c753d72 100644 --- a/pkg/cmd/server/api/types.go +++ b/pkg/cmd/server/api/types.go @@ -48,7 +48,9 @@ type MasterConfig struct { CORSAllowedOrigins []string // EtcdClientInfo contains information about how to connect to etcd - EtcdClientInfo RemoteConnectionInfo + EtcdClientInfo EtcdConnectionInfo + // KubeletClientInfo contains information about how to connect to kubelets + KubeletClientInfo KubeletConnectionInfo // KubernetesMasterConfig, if present start the kubernetes master in this process KubernetesMasterConfig *KubernetesMasterConfig @@ -89,12 +91,29 @@ type ImageConfig struct { } type RemoteConnectionInfo struct { - // URL is the URL for etcd + // URL is the remote URL to connect to URL string - // CA is the CA for confirming that the server at the etcdURL is the actual server + // CA is the CA for verifying TLS connections CA string - // EtcdClientCertInfo is the TLS client cert information for securing communication to etcd - // this is anonymous so that we can inline it for serialization + // CertInfo is the TLS client cert information to present + ClientCert CertInfo +} + +type KubeletConnectionInfo struct { + // Port is the port to connect to kubelets on + Port uint + // CA is the CA for verifying TLS connections to kubelets + CA string + // CertInfo is the TLS client cert information for securing communication to kubelets + ClientCert CertInfo +} + +type EtcdConnectionInfo struct { + // URLs are the URLs for etcd + URLs []string + // CA is a file containing trusted roots for the etcd server certificates + CA string + // ClientCert is the TLS client cert information for securing communication to etcd ClientCert CertInfo } @@ -264,10 +283,14 @@ const ( var ValidGrantHandlerTypes = util.NewStringSet(string(GrantHandlerAuto), string(GrantHandlerPrompt), string(GrantHandlerDeny)) type EtcdConfig struct { + // ServingInfo describes how to start serving the etcd master ServingInfo ServingInfo - - PeerAddress string - MasterAddress string + // Address is the advertised host:port for client connections to etcd + Address string + // PeerServingInfo describes how to start serving the etcd peer + PeerServingInfo ServingInfo + // PeerAddress is the advertised host:port for peer connections to etcd + PeerAddress string // StorageDir indicates where to save the etcd data StorageDir string } diff --git a/pkg/cmd/server/api/v1/conversions.go b/pkg/cmd/server/api/v1/conversions.go index 8d72efdd4431..06a6b52cc9c2 100644 --- a/pkg/cmd/server/api/v1/conversions.go +++ b/pkg/cmd/server/api/v1/conversions.go @@ -22,15 +22,29 @@ func init() { out.KeyFile = in.ServerCert.KeyFile return nil }, - func(in *RemoteConnectionInfo, out *newer.RemoteConnectionInfo, s conversion.Scope) error { - out.URL = in.URL + func(in *EtcdConnectionInfo, out *newer.EtcdConnectionInfo, s conversion.Scope) error { + out.URLs = in.URLs out.CA = in.CA out.ClientCert.CertFile = in.CertFile out.ClientCert.KeyFile = in.KeyFile return nil }, - func(in *newer.RemoteConnectionInfo, out *RemoteConnectionInfo, s conversion.Scope) error { - out.URL = in.URL + func(in *newer.EtcdConnectionInfo, out *EtcdConnectionInfo, s conversion.Scope) error { + out.URLs = in.URLs + out.CA = in.CA + out.CertFile = in.ClientCert.CertFile + out.KeyFile = in.ClientCert.KeyFile + return nil + }, + func(in *KubeletConnectionInfo, out *newer.KubeletConnectionInfo, s conversion.Scope) error { + out.Port = in.Port + out.CA = in.CA + out.ClientCert.CertFile = in.CertFile + out.ClientCert.KeyFile = in.KeyFile + return nil + }, + func(in *newer.KubeletConnectionInfo, out *KubeletConnectionInfo, s conversion.Scope) error { + out.Port = in.Port out.CA = in.CA out.CertFile = in.ClientCert.CertFile out.KeyFile = in.ClientCert.KeyFile diff --git a/pkg/cmd/server/api/v1/types.go b/pkg/cmd/server/api/v1/types.go index 9d62c27e5e0e..29f233a8efbf 100644 --- a/pkg/cmd/server/api/v1/types.go +++ b/pkg/cmd/server/api/v1/types.go @@ -47,7 +47,9 @@ type MasterConfig struct { CORSAllowedOrigins []string `json:"corsAllowedOrigins"` // EtcdClientInfo contains information about how to connect to etcd - EtcdClientInfo RemoteConnectionInfo `json:"etcdClientInfo"` + EtcdClientInfo EtcdConnectionInfo `json:"etcdClientInfo"` + // KubeletClientInfo contains information about how to connect to kubelets + KubeletClientInfo KubeletConnectionInfo `json:"kubeletClientInfo"` // KubernetesMasterConfig, if present start the kubernetes master in this process KubernetesMasterConfig *KubernetesMasterConfig `json:"kubernetesMasterConfig"` @@ -84,11 +86,31 @@ type ImageConfig struct { } type RemoteConnectionInfo struct { - // URL is the URL for etcd + // URL is the remote URL to connect to URL string `json:"url"` - // CA is the CA for confirming that the server at the etcdURL is the actual server + // CA is the CA for verifying TLS connections CA string `json:"ca"` - // EtcdClientCertInfo is the TLS client cert information for securing communication to etcd + // CertInfo is the TLS client cert information to present + // this is anonymous so that we can inline it for serialization + CertInfo `json:",inline"` +} + +type KubeletConnectionInfo struct { + // Port is the port to connect to kubelets on + Port uint `json:"port"` + // CA is the CA for verifying TLS connections to kubelets + CA string `json:"ca"` + // CertInfo is the TLS client cert information for securing communication to kubelets + // this is anonymous so that we can inline it for serialization + CertInfo `json:",inline"` +} + +type EtcdConnectionInfo struct { + // URLs are the URLs for etcd + URLs []string `json:"urls"` + // CA is a file containing trusted roots for the etcd server certificates + CA string `json:"ca"` + // CertInfo is the TLS client cert information for securing communication to etcd // this is anonymous so that we can inline it for serialization CertInfo `json:",inline"` } @@ -243,11 +265,16 @@ const ( ) type EtcdConfig struct { + // ServingInfo describes how to start serving the etcd master ServingInfo ServingInfo `json:"servingInfo"` - - PeerAddress string `json:"peerAddress"` - MasterAddress string `json:"masterAddress"` - StorageDir string `json:"storageDirectory"` + // Address is the advertised host:port for client connections to etcd + Address string `json:"address"` + // PeerServingInfo describes how to start serving the etcd peer + PeerServingInfo ServingInfo `json:"peerServingInfo"` + // PeerAddress is the advertised host:port for peer connections to etcd + PeerAddress string `json:"peerAddress"` + + StorageDir string `json:"storageDirectory"` } type KubernetesMasterConfig struct { diff --git a/pkg/cmd/server/api/validation/allinone.go b/pkg/cmd/server/api/validation/allinone.go new file mode 100644 index 000000000000..2af2b053915d --- /dev/null +++ b/pkg/cmd/server/api/validation/allinone.go @@ -0,0 +1,19 @@ +package validation + +import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors" + + "github.com/openshift/origin/pkg/cmd/server/api" +) + +func ValidateAllInOneConfig(master *api.MasterConfig, node *api.NodeConfig) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + + allErrs = append(allErrs, ValidateMasterConfig(master).Prefix("masterConfig")...) + + allErrs = append(allErrs, ValidateNodeConfig(node).Prefix("nodeConfig")...) + + // Validation between the configs + + return allErrs +} diff --git a/pkg/cmd/server/api/validation/etcd.go b/pkg/cmd/server/api/validation/etcd.go new file mode 100644 index 000000000000..f34594ae6fde --- /dev/null +++ b/pkg/cmd/server/api/validation/etcd.go @@ -0,0 +1,73 @@ +package validation + +import ( + "fmt" + "strings" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors" + "github.com/openshift/origin/pkg/cmd/server/api" +) + +// ValidateEtcdConnectionInfo validates the connection info. If a server EtcdConfig is provided, +// it ensures the connection info includes a URL for it, and has a client cert/key if the server requires +// client certificate authentication +func ValidateEtcdConnectionInfo(config api.EtcdConnectionInfo, server *api.EtcdConfig) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + + if len(config.URLs) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("urls")) + } + for i, u := range config.URLs { + _, urlErrs := ValidateURL(u, fmt.Sprintf("urls[%d]", i)) + if len(urlErrs) > 0 { + allErrs = append(allErrs, urlErrs...) + } + } + + if len(config.CA) > 0 { + allErrs = append(allErrs, ValidateFile(config.CA, "ca")...) + } + allErrs = append(allErrs, ValidateCertInfo(config.ClientCert, false)...) + + // If we have server config info, make sure the client connection info will work with it + if server != nil { + var builtInAddress string + if api.UseTLS(server.ServingInfo) { + builtInAddress = fmt.Sprintf("https://%s", server.Address) + } else { + builtInAddress = fmt.Sprintf("http://%s", server.Address) + } + + // Require a client cert to connect to an etcd that requires client certs + if len(server.ServingInfo.ClientCA) > 0 { + if len(config.ClientCert.CertFile) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("certFile")) + } + } + + // Require the etcdClientInfo to include the address of the internal etcd + clientURLs := util.NewStringSet(config.URLs...) + if !clientURLs.Has(builtInAddress) { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("urls", strings.Join(clientURLs.List(), ","), fmt.Sprintf("must include the etcd address %s", builtInAddress))) + } + } + + return allErrs +} + +func ValidateEtcdConfig(config *api.EtcdConfig) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + + allErrs = append(allErrs, ValidateServingInfo(config.ServingInfo).Prefix("servingInfo")...) + allErrs = append(allErrs, ValidateServingInfo(config.PeerServingInfo).Prefix("peerServingInfo")...) + + allErrs = append(allErrs, ValidateHostPort(config.Address, "address")...) + allErrs = append(allErrs, ValidateHostPort(config.PeerAddress, "peerAddress")...) + + if len(config.StorageDir) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("storageDirectory")) + } + + return allErrs +} diff --git a/pkg/cmd/server/api/validation/master.go b/pkg/cmd/server/api/validation/master.go new file mode 100644 index 000000000000..f8a50bf57cff --- /dev/null +++ b/pkg/cmd/server/api/validation/master.go @@ -0,0 +1,151 @@ +package validation + +import ( + "net" + "net/url" + "strings" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors" + + "github.com/openshift/origin/pkg/cmd/server/api" +) + +func ValidateMasterConfig(config *api.MasterConfig) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + + if config.AssetConfig != nil { + allErrs = append(allErrs, ValidateAssetConfig(config.AssetConfig).Prefix("assetConfig")...) + colocated := config.AssetConfig.ServingInfo.BindAddress == config.ServingInfo.BindAddress + if colocated { + publicURL, _ := url.Parse(config.AssetConfig.PublicURL) + if publicURL.Path == "/" { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("assetConfig.publicURL", config.AssetConfig.PublicURL, "path can not be / when colocated with master API")) + } + } + + if config.OAuthConfig != nil { + if config.OAuthConfig.AssetPublicURL != config.AssetConfig.PublicURL { + allErrs = append(allErrs, + fielderrors.NewFieldInvalid("assetConfig.publicURL", config.AssetConfig.PublicURL, "must match oauthConfig.assetPublicURL"), + fielderrors.NewFieldInvalid("oauthConfig.assetPublicURL", config.OAuthConfig.AssetPublicURL, "must match assetConfig.publicURL"), + ) + } + } + + // TODO warn when the CORS list does not include the assetConfig.publicURL host:port + // only warn cause they could handle CORS headers themselves in a proxy + } + + if config.DNSConfig != nil { + allErrs = append(allErrs, ValidateHostPort(config.DNSConfig.BindAddress, "bindAddress").Prefix("dnsConfig")...) + } + + if config.EtcdConfig != nil { + etcdConfigErrs := ValidateEtcdConfig(config.EtcdConfig).Prefix("etcdConfig") + allErrs = append(allErrs, etcdConfigErrs...) + + if len(etcdConfigErrs) == 0 { + // Validate the etcdClientInfo with the internal etcdConfig + allErrs = append(allErrs, ValidateEtcdConnectionInfo(config.EtcdClientInfo, config.EtcdConfig).Prefix("etcdClientInfo")...) + } else { + // Validate the etcdClientInfo by itself + allErrs = append(allErrs, ValidateEtcdConnectionInfo(config.EtcdClientInfo, nil).Prefix("etcdClientInfo")...) + } + } else { + // Validate the etcdClientInfo by itself + allErrs = append(allErrs, ValidateEtcdConnectionInfo(config.EtcdClientInfo, nil).Prefix("etcdClientInfo")...) + } + + allErrs = append(allErrs, ValidateImageConfig(config.ImageConfig).Prefix("imageConfig")...) + + allErrs = append(allErrs, ValidateKubeletConnectionInfo(config.KubeletClientInfo).Prefix("kubeletClientInfo")...) + + if config.KubernetesMasterConfig != nil { + allErrs = append(allErrs, ValidateKubernetesMasterConfig(config.KubernetesMasterConfig).Prefix("kubernetesMasterConfig")...) + } + + allErrs = append(allErrs, ValidateKubeConfig(config.MasterClients.DeployerKubeConfig, "deployerKubeConfig").Prefix("masterClients")...) + allErrs = append(allErrs, ValidateKubeConfig(config.MasterClients.OpenShiftLoopbackKubeConfig, "openShiftLoopbackKubeConfig").Prefix("masterClients")...) + allErrs = append(allErrs, ValidateKubeConfig(config.MasterClients.KubernetesKubeConfig, "kubernetesKubeConfig").Prefix("masterClients")...) + + allErrs = append(allErrs, ValidatePolicyConfig(config.PolicyConfig).Prefix("policyConfig")...) + if config.OAuthConfig != nil { + allErrs = append(allErrs, ValidateOAuthConfig(config.OAuthConfig).Prefix("oauthConfig")...) + } + + allErrs = append(allErrs, ValidateServingInfo(config.ServingInfo).Prefix("servingInfo")...) + + return allErrs +} + +func ValidateAssetConfig(config *api.AssetConfig) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + + allErrs = append(allErrs, ValidateServingInfo(config.ServingInfo).Prefix("servingInfo")...) + + urlObj, urlErrs := ValidateURL(config.PublicURL, "publicURL") + if len(urlErrs) > 0 { + allErrs = append(allErrs, urlErrs...) + } + if urlObj != nil { + if !strings.HasSuffix(urlObj.Path, "/") { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("publicURL", config.PublicURL, "must have a trailing slash in path")) + } + } + + return allErrs +} + +func ValidateImageConfig(config api.ImageConfig) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + + if len(config.Format) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("format")) + } + + return allErrs +} + +func ValidateKubeletConnectionInfo(config api.KubeletConnectionInfo) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + if config.Port == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("port")) + } + + if len(config.CA) > 0 { + allErrs = append(allErrs, ValidateFile(config.CA, "ca")...) + } + allErrs = append(allErrs, ValidateCertInfo(config.ClientCert, false)...) + + return allErrs +} + +func ValidateKubernetesMasterConfig(config *api.KubernetesMasterConfig) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + + if len(config.MasterIP) > 0 { + allErrs = append(allErrs, ValidateSpecifiedIP(config.MasterIP, "masterIP")...) + } + + if len(config.ServicesSubnet) > 0 { + if _, _, err := net.ParseCIDR(strings.TrimSpace(config.ServicesSubnet)); err != nil { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("servicesSubnet", config.ServicesSubnet, "must be a valid CIDR notation IP range (e.g. 172.30.17.0/24)")) + } + } + + if len(config.SchedulerConfigFile) > 0 { + allErrs = append(allErrs, ValidateFile(config.SchedulerConfigFile, "schedulerConfigFile")...) + } + + return allErrs +} + +func ValidatePolicyConfig(config api.PolicyConfig) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + + allErrs = append(allErrs, ValidateFile(config.BootstrapPolicyFile, "bootstrapPolicyFile")...) + allErrs = append(allErrs, ValidateNamespace(config.MasterAuthorizationNamespace, "masterAuthorizationNamespace")...) + allErrs = append(allErrs, ValidateNamespace(config.OpenShiftSharedResourcesNamespace, "openShiftSharedResourcesNamespace")...) + + return allErrs +} diff --git a/pkg/cmd/server/api/validation/node.go b/pkg/cmd/server/api/validation/node.go new file mode 100644 index 000000000000..80b3a864ebdc --- /dev/null +++ b/pkg/cmd/server/api/validation/node.go @@ -0,0 +1,28 @@ +package validation + +import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors" + + "github.com/openshift/origin/pkg/cmd/server/api" +) + +func ValidateNodeConfig(config *api.NodeConfig) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + + if len(config.NodeName) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("nodeName")) + } + + allErrs = append(allErrs, ValidateServingInfo(config.ServingInfo).Prefix("servingInfo")...) + allErrs = append(allErrs, ValidateKubeConfig(config.MasterKubeConfig, "masterKubeConfig")...) + + if len(config.DNSIP) > 0 { + allErrs = append(allErrs, ValidateSpecifiedIP(config.DNSIP, "dnsIP")...) + } + + if len(config.NetworkContainerImage) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("networkContainerImage")) + } + + return allErrs +} diff --git a/pkg/cmd/server/api/validation/oauth.go b/pkg/cmd/server/api/validation/oauth.go new file mode 100644 index 000000000000..a537f14a7b22 --- /dev/null +++ b/pkg/cmd/server/api/validation/oauth.go @@ -0,0 +1,125 @@ +package validation + +import ( + "fmt" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors" + "github.com/openshift/origin/pkg/cmd/server/api" +) + +func ValidateOAuthConfig(config *api.OAuthConfig) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + + if len(config.MasterURL) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("masterURL")) + } + + if len(config.MasterPublicURL) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("masterPublicURL")) + } + + if len(config.AssetPublicURL) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("assetPublicURL")) + } + + if config.SessionConfig != nil { + allErrs = append(allErrs, ValidateSessionConfig(config.SessionConfig).Prefix("sessionConfig")...) + } + + allErrs = append(allErrs, ValidateGrantConfig(config.GrantConfig).Prefix("grantConfig")...) + + redirectingIdentityProviders := []int{} + for i, identityProvider := range config.IdentityProviders { + if identityProvider.Usage.UseAsLogin { + redirectingIdentityProviders = append(redirectingIdentityProviders, i) + + if api.IsPasswordAuthenticator(identityProvider) { + if config.SessionConfig == nil { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("sessionConfig", config, "sessionConfig is required if a password identity provider is used for browser based login")) + } + } + } + + allErrs = append(allErrs, ValidateIdentityProvider(identityProvider).Prefix(fmt.Sprintf("identityProvider[%d]", i))...) + } + + if len(redirectingIdentityProviders) > 1 { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("identityProviders", config.IdentityProviders, fmt.Sprintf("only one identity provider can support login for a browser, found: %v", redirectingIdentityProviders))) + } + + return allErrs +} + +func ValidateIdentityProvider(identityProvider api.IdentityProvider) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + + if len(identityProvider.Usage.ProviderName) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("usage.providerName")) + } + + if !api.IsIdentityProviderType(identityProvider.Provider) { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("provider", identityProvider.Provider, fmt.Sprintf("%v is invalid in this context", identityProvider.Provider))) + } else { + switch provider := identityProvider.Provider.Object.(type) { + case (*api.RequestHeaderIdentityProvider): + if len(provider.ClientCA) > 0 { + allErrs = append(allErrs, ValidateFile(provider.ClientCA, "provider.clientCA")...) + } + if len(provider.Headers) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("provider.headers")) + } + if identityProvider.Usage.UseAsChallenger { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("provider.useAsChallenger", identityProvider.Usage.UseAsChallenger, "request header providers cannot be used for challenges")) + } + if identityProvider.Usage.UseAsLogin { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("provider.useAsLogin", identityProvider.Usage.UseAsChallenger, "request header providers cannot be used for browser login")) + } + + case (*api.BasicAuthPasswordIdentityProvider): + allErrs = append(allErrs, ValidateRemoteConnectionInfo(provider.RemoteConnectionInfo).Prefix("provider")...) + + case (*api.HTPasswdPasswordIdentityProvider): + allErrs = append(allErrs, ValidateFile(provider.File, "provider.file")...) + + case (*api.OAuthRedirectingIdentityProvider): + if len(provider.ClientID) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("provider.clientID")) + } + if len(provider.ClientSecret) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("provider.clientSecret")) + } + if !api.IsOAuthProviderType(provider.Provider) { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("provider.provider", provider.Provider, fmt.Sprintf("%v is invalid in this context", identityProvider.Provider))) + } + if identityProvider.Usage.UseAsChallenger { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("provider.useAsChallenger", identityProvider.Usage.UseAsChallenger, "oauth providers cannot be used for challenges")) + } + } + + } + + return allErrs +} + +func ValidateGrantConfig(config api.GrantConfig) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + + if !api.ValidGrantHandlerTypes.Has(string(config.Method)) { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("grantConfig.method", config.Method, fmt.Sprintf("must be one of: %v", api.ValidGrantHandlerTypes.List()))) + } + + return allErrs +} + +func ValidateSessionConfig(config *api.SessionConfig) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + + if len(config.SessionSecrets) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("sessionSecrets")) + } + if len(config.SessionName) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("sessionName")) + } + + return allErrs +} diff --git a/pkg/cmd/server/api/validation/validation.go b/pkg/cmd/server/api/validation/validation.go index e77d08a5c1e3..38cc8669d0b9 100644 --- a/pkg/cmd/server/api/validation/validation.go +++ b/pkg/cmd/server/api/validation/validation.go @@ -1,11 +1,9 @@ package validation import ( - "fmt" "net" "net/url" "os" - "strings" kvalidation "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors" @@ -13,154 +11,65 @@ import ( "github.com/openshift/origin/pkg/cmd/server/api" ) -func ValidateBindAddress(bindAddress string) fielderrors.ValidationErrorList { +func ValidateHostPort(value string, field string) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} - if len(bindAddress) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("bindAddress")) - } else if _, _, err := net.SplitHostPort(bindAddress); err != nil { - allErrs = append(allErrs, fielderrors.NewFieldInvalid("bindAddress", bindAddress, "must be a host:port")) + if len(value) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired(field)) + } else if _, _, err := net.SplitHostPort(value); err != nil { + allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, value, "must be a host:port")) } return allErrs } -func ValidateServingInfo(info api.ServingInfo) fielderrors.ValidationErrorList { +func ValidateCertInfo(certInfo api.CertInfo, required bool) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} - allErrs = append(allErrs, ValidateBindAddress(info.BindAddress)...) - allErrs = append(allErrs, ValidateCertInfo(info.ServerCert)...) - - if len(info.ClientCA) > 0 { - if (len(info.ServerCert.CertFile) == 0) || (len(info.ServerCert.KeyFile) == 0) { - allErrs = append(allErrs, fielderrors.NewFieldInvalid("clientCA", info.ClientCA, "cannot specify a clientCA without a certFile")) + if required || len(certInfo.CertFile) > 0 || len(certInfo.KeyFile) > 0 { + if len(certInfo.CertFile) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("certFile")) + } + if len(certInfo.KeyFile) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("keyFile")) } - - allErrs = append(allErrs, ValidateFile(info.ClientCA, "clientCA")...) - } - - return allErrs -} - -func ValidateKubeConfig(path string, field string) fielderrors.ValidationErrorList { - allErrs := fielderrors.ValidationErrorList{} - - allErrs = append(allErrs, ValidateFile(path, field)...) - // TODO: load and parse - - return allErrs -} - -func ValidateKubernetesMasterConfig(config *api.KubernetesMasterConfig) fielderrors.ValidationErrorList { - allErrs := fielderrors.ValidationErrorList{} - - if len(config.MasterIP) > 0 { - allErrs = append(allErrs, ValidateSpecifiedIP(config.MasterIP, "masterIP")...) } - if len(config.ServicesSubnet) > 0 { - if _, _, err := net.ParseCIDR(strings.TrimSpace(config.ServicesSubnet)); err != nil { - allErrs = append(allErrs, fielderrors.NewFieldInvalid("servicesSubnet", config.ServicesSubnet, "must be a valid CIDR notation IP range (e.g. 172.30.17.0/24)")) - } + if len(certInfo.CertFile) > 0 { + allErrs = append(allErrs, ValidateFile(certInfo.CertFile, "certFile")...) } - if len(config.SchedulerConfigFile) > 0 { - allErrs = append(allErrs, ValidateFile(config.SchedulerConfigFile, "schedulerConfigFile")...) + if len(certInfo.KeyFile) > 0 { + allErrs = append(allErrs, ValidateFile(certInfo.KeyFile, "keyFile")...) } return allErrs } -func ValidateOAuthConfig(config *api.OAuthConfig) fielderrors.ValidationErrorList { +func ValidateServingInfo(info api.ServingInfo) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} - if len(config.MasterURL) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("masterURL")) - } - - if len(config.MasterPublicURL) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("masterPublicURL")) - } - - if len(config.AssetPublicURL) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("assetPublicURL")) - } + allErrs = append(allErrs, ValidateHostPort(info.BindAddress, "bindAddress")...) + allErrs = append(allErrs, ValidateCertInfo(info.ServerCert, false)...) - if config.SessionConfig != nil { - allErrs = append(allErrs, ValidateSessionConfig(config.SessionConfig).Prefix("sessionConfig")...) - } - - allErrs = append(allErrs, ValidateGrantConfig(config.GrantConfig).Prefix("grantConfig")...) - - redirectingIdentityProviders := []int{} - for i, identityProvider := range config.IdentityProviders { - if identityProvider.Usage.UseAsLogin { - redirectingIdentityProviders = append(redirectingIdentityProviders, i) - - if api.IsPasswordAuthenticator(identityProvider) { - if config.SessionConfig == nil { - allErrs = append(allErrs, fielderrors.NewFieldInvalid("sessionConfig", config, "sessionConfig is required if a password identity provider is used for browser based login")) - } - } + if len(info.ServerCert.CertFile) > 0 { + if len(info.ClientCA) > 0 { + allErrs = append(allErrs, ValidateFile(info.ClientCA, "clientCA")...) + } + } else { + if len(info.ClientCA) > 0 { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("clientCA", info.ClientCA, "cannot specify a clientCA without a certFile")) } - - allErrs = append(allErrs, ValidateIdentityProvider(identityProvider).Prefix(fmt.Sprintf("identityProvider[%d]", i))...) - } - - if len(redirectingIdentityProviders) > 1 { - allErrs = append(allErrs, fielderrors.NewFieldInvalid("identityProviders", config.IdentityProviders, fmt.Sprintf("only one identity provider can support login for a browser, found: %v", redirectingIdentityProviders))) } return allErrs } -func ValidateIdentityProvider(identityProvider api.IdentityProvider) fielderrors.ValidationErrorList { +func ValidateKubeConfig(path string, field string) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} - if len(identityProvider.Usage.ProviderName) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("usage.providerName")) - } - - if !api.IsIdentityProviderType(identityProvider.Provider) { - allErrs = append(allErrs, fielderrors.NewFieldInvalid("provider", identityProvider.Provider, fmt.Sprintf("%v is invalid in this context", identityProvider.Provider))) - } else { - switch provider := identityProvider.Provider.Object.(type) { - case (*api.RequestHeaderIdentityProvider): - if len(provider.ClientCA) > 0 { - allErrs = append(allErrs, ValidateFile(provider.ClientCA, "provider.clientCA")...) - } - if len(provider.Headers) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("provider.headers")) - } - if identityProvider.Usage.UseAsChallenger { - allErrs = append(allErrs, fielderrors.NewFieldInvalid("provider.useAsChallenger", identityProvider.Usage.UseAsChallenger, "request header providers cannot be used for challenges")) - } - if identityProvider.Usage.UseAsLogin { - allErrs = append(allErrs, fielderrors.NewFieldInvalid("provider.useAsLogin", identityProvider.Usage.UseAsChallenger, "request header providers cannot be used for browser login")) - } - - case (*api.BasicAuthPasswordIdentityProvider): - allErrs = append(allErrs, ValidateRemoteConnectionInfo(provider.RemoteConnectionInfo).Prefix("provider")...) - - case (*api.HTPasswdPasswordIdentityProvider): - allErrs = append(allErrs, ValidateFile(provider.File, "provider.file")...) - - case (*api.OAuthRedirectingIdentityProvider): - if len(provider.ClientID) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("provider.clientID")) - } - if len(provider.ClientSecret) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("provider.clientSecret")) - } - if !api.IsOAuthProviderType(provider.Provider) { - allErrs = append(allErrs, fielderrors.NewFieldInvalid("provider.provider", provider.Provider, fmt.Sprintf("%v is invalid in this context", identityProvider.Provider))) - } - if identityProvider.Usage.UseAsChallenger { - allErrs = append(allErrs, fielderrors.NewFieldInvalid("provider.useAsChallenger", identityProvider.Usage.UseAsChallenger, "oauth providers cannot be used for challenges")) - } - } - - } + allErrs = append(allErrs, ValidateFile(path, field)...) + // TODO: load and parse return allErrs } @@ -176,52 +85,7 @@ func ValidateRemoteConnectionInfo(remoteConnectionInfo api.RemoteConnectionInfo) allErrs = append(allErrs, ValidateFile(remoteConnectionInfo.CA, "ca")...) } - allErrs = append(allErrs, ValidateCertInfo(remoteConnectionInfo.ClientCert)...) - - return allErrs -} - -func ValidateCertInfo(certInfo api.CertInfo) fielderrors.ValidationErrorList { - allErrs := fielderrors.ValidationErrorList{} - - if len(certInfo.CertFile) > 0 { - if len(certInfo.KeyFile) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("keyFile")) - } - - allErrs = append(allErrs, ValidateFile(certInfo.CertFile, "certFile")...) - } - - if len(certInfo.KeyFile) > 0 { - if len(certInfo.CertFile) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("certFile")) - } - - allErrs = append(allErrs, ValidateFile(certInfo.KeyFile, "keyFile")...) - } - - return allErrs -} - -func ValidateGrantConfig(config api.GrantConfig) fielderrors.ValidationErrorList { - allErrs := fielderrors.ValidationErrorList{} - - if !api.ValidGrantHandlerTypes.Has(string(config.Method)) { - allErrs = append(allErrs, fielderrors.NewFieldInvalid("grantConfig.method", config.Method, fmt.Sprintf("must be one of: %v", api.ValidGrantHandlerTypes.List()))) - } - - return allErrs -} - -func ValidateSessionConfig(config *api.SessionConfig) fielderrors.ValidationErrorList { - allErrs := fielderrors.ValidationErrorList{} - - if len(config.SessionSecrets) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("sessionSecrets")) - } - if len(config.SessionName) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("sessionName")) - } + allErrs = append(allErrs, ValidateCertInfo(remoteConnectionInfo.ClientCert, false)...) return allErrs } @@ -256,78 +120,6 @@ func ValidateURL(urlString string, field string) (*url.URL, fielderrors.Validati return urlObj, allErrs } -func ValidateAssetConfig(config *api.AssetConfig) fielderrors.ValidationErrorList { - allErrs := fielderrors.ValidationErrorList{} - - allErrs = append(allErrs, ValidateServingInfo(config.ServingInfo).Prefix("servingInfo")...) - - urlObj, urlErrs := ValidateURL(config.PublicURL, "publicURL") - if len(urlErrs) > 0 { - allErrs = append(allErrs, urlErrs...) - } - if urlObj != nil { - if !strings.HasSuffix(urlObj.Path, "/") { - allErrs = append(allErrs, fielderrors.NewFieldInvalid("publicURL", config.PublicURL, "must have a trailing slash in path")) - } - } - - return allErrs -} - -func ValidateMasterConfig(config *api.MasterConfig) fielderrors.ValidationErrorList { - allErrs := fielderrors.ValidationErrorList{} - - allErrs = append(allErrs, ValidateServingInfo(config.ServingInfo).Prefix("servingInfo")...) - - if config.AssetConfig != nil { - allErrs = append(allErrs, ValidateAssetConfig(config.AssetConfig).Prefix("assetConfig")...) - colocated := config.AssetConfig.ServingInfo.BindAddress == config.ServingInfo.BindAddress - if colocated { - publicURL, _ := url.Parse(config.AssetConfig.PublicURL) - if publicURL.Path == "/" { - allErrs = append(allErrs, fielderrors.NewFieldInvalid("assetConfig.publicURL", config.AssetConfig.PublicURL, "path can not be / when colocated with master API")) - } - } - - if config.OAuthConfig != nil && config.OAuthConfig.AssetPublicURL != config.AssetConfig.PublicURL { - allErrs = append(allErrs, - fielderrors.NewFieldInvalid("assetConfig.publicURL", config.AssetConfig.PublicURL, "must match oauthConfig.assetPublicURL"), - fielderrors.NewFieldInvalid("oauthConfig.assetPublicURL", config.OAuthConfig.AssetPublicURL, "must match assetConfig.publicURL"), - ) - } - - // TODO warn when the CORS list does not include the assetConfig.publicURL host:port - // only warn cause they could handle CORS headers themselves in a proxy - } - - if config.DNSConfig != nil { - allErrs = append(allErrs, ValidateBindAddress(config.DNSConfig.BindAddress).Prefix("dnsConfig")...) - } - - if config.KubernetesMasterConfig != nil { - allErrs = append(allErrs, ValidateKubernetesMasterConfig(config.KubernetesMasterConfig).Prefix("kubernetesMasterConfig")...) - } - - allErrs = append(allErrs, ValidatePolicyConfig(config.PolicyConfig).Prefix("policyConfig")...) - allErrs = append(allErrs, ValidateOAuthConfig(config.OAuthConfig).Prefix("oauthConfig")...) - - allErrs = append(allErrs, ValidateKubeConfig(config.MasterClients.DeployerKubeConfig, "deployerKubeConfig").Prefix("masterClients")...) - allErrs = append(allErrs, ValidateKubeConfig(config.MasterClients.OpenShiftLoopbackKubeConfig, "openShiftLoopbackKubeConfig").Prefix("masterClients")...) - allErrs = append(allErrs, ValidateKubeConfig(config.MasterClients.KubernetesKubeConfig, "kubernetesKubeConfig").Prefix("masterClients")...) - - return allErrs -} - -func ValidatePolicyConfig(config api.PolicyConfig) fielderrors.ValidationErrorList { - allErrs := fielderrors.ValidationErrorList{} - - allErrs = append(allErrs, ValidateFile(config.BootstrapPolicyFile, "bootstrapPolicyFile")...) - allErrs = append(allErrs, ValidateNamespace(config.MasterAuthorizationNamespace, "masterAuthorizationNamespace")...) - allErrs = append(allErrs, ValidateNamespace(config.OpenShiftSharedResourcesNamespace, "openShiftSharedResourcesNamespace")...) - - return allErrs -} - func ValidateNamespace(namespace, field string) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} @@ -340,27 +132,6 @@ func ValidateNamespace(namespace, field string) fielderrors.ValidationErrorList return allErrs } -func ValidateNodeConfig(config *api.NodeConfig) fielderrors.ValidationErrorList { - allErrs := fielderrors.ValidationErrorList{} - - if len(config.NodeName) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("nodeName")) - } - - allErrs = append(allErrs, ValidateServingInfo(config.ServingInfo).Prefix("servingInfo")...) - allErrs = append(allErrs, ValidateKubeConfig(config.MasterKubeConfig, "masterKubeConfig")...) - - if len(config.DNSIP) > 0 { - allErrs = append(allErrs, ValidateSpecifiedIP(config.DNSIP, "dnsIP")...) - } - - if len(config.NetworkContainerImage) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("networkContainerImage")) - } - - return allErrs -} - func ValidateFile(path string, field string) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} @@ -372,15 +143,3 @@ func ValidateFile(path string, field string) fielderrors.ValidationErrorList { return allErrs } - -func ValidateAllInOneConfig(master *api.MasterConfig, node *api.NodeConfig) fielderrors.ValidationErrorList { - allErrs := fielderrors.ValidationErrorList{} - - allErrs = append(allErrs, ValidateMasterConfig(master).Prefix("masterConfig")...) - - allErrs = append(allErrs, ValidateNodeConfig(node).Prefix("nodeConfig")...) - - // Validation between the configs - - return allErrs -} diff --git a/pkg/cmd/server/crypto/crypto.go b/pkg/cmd/server/crypto/crypto.go index 102b715e5243..1d3d409f5b5f 100644 --- a/pkg/cmd/server/crypto/crypto.go +++ b/pkg/cmd/server/crypto/crypto.go @@ -101,21 +101,41 @@ func GetTLSCertificateConfig(certFile, keyFile string) (*TLSCertificateConfig, e } func CertPoolFromFile(filename string) (*x509.CertPool, error) { + pool := x509.NewCertPool() + if len(filename) == 0 { + return pool, nil + } + pemBlock, err := ioutil.ReadFile(filename) if err != nil { return nil, err } + certs, err := certsFromPEM(pemBlock) if err != nil { return nil, fmt.Errorf("Error reading %s: %s", filename, err) } - roots := x509.NewCertPool() - for _, root := range certs { - roots.AddCert(root) + for _, cert := range certs { + pool.AddCert(cert) } - return roots, nil + return pool, nil +} + +func CertificatesFromFile(file string) ([]*x509.Certificate, error) { + if len(file) == 0 { + return nil, nil + } + pemBlock, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + certs, err := certsFromPEM(pemBlock) + if err != nil { + return nil, fmt.Errorf("Error reading %s: %s", file, err) + } + return certs, nil } func certsFromPEM(pemCerts []byte) ([]*x509.Certificate, error) { diff --git a/pkg/cmd/server/etcd/etcd.go b/pkg/cmd/server/etcd/etcd.go index 7c66d0293ac9..3ad8eb75d685 100644 --- a/pkg/cmd/server/etcd/etcd.go +++ b/pkg/cmd/server/etcd/etcd.go @@ -13,23 +13,33 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/openshift/origin/pkg/api/latest" + configapi "github.com/openshift/origin/pkg/cmd/server/api" ) -// Config is an object that can run an etcd server -type Config struct { - BindAddr string - PeerBindAddr string - MasterAddr string - EtcdDir string -} +// RunEtcd starts an etcd server and runs it forever +func RunEtcd(etcdServerConfig *configapi.EtcdConfig) { -// Run starts an etcd server and runs it forever -func (c *Config) Run() { config := etcdconfig.New() - config.Addr = c.MasterAddr - config.BindAddr = c.BindAddr - config.Peer.BindAddr = c.PeerBindAddr - config.DataDir = c.EtcdDir + + config.Addr = etcdServerConfig.Address + config.BindAddr = etcdServerConfig.ServingInfo.BindAddress + + if configapi.UseTLS(etcdServerConfig.ServingInfo) { + config.CAFile = etcdServerConfig.ServingInfo.ClientCA + config.CertFile = etcdServerConfig.ServingInfo.ServerCert.CertFile + config.KeyFile = etcdServerConfig.ServingInfo.ServerCert.KeyFile + } + + config.Peer.Addr = etcdServerConfig.PeerAddress + config.Peer.BindAddr = etcdServerConfig.PeerServingInfo.BindAddress + + if configapi.UseTLS(etcdServerConfig.PeerServingInfo) { + config.Peer.CAFile = etcdServerConfig.PeerServingInfo.ClientCA + config.Peer.CertFile = etcdServerConfig.PeerServingInfo.ServerCert.CertFile + config.Peer.KeyFile = etcdServerConfig.PeerServingInfo.ServerCert.KeyFile + } + + config.DataDir = etcdServerConfig.StorageDir config.Name = "openshift.local" server := etcd.New(config) @@ -44,9 +54,29 @@ func (c *Config) Run() { // getAndTestEtcdClient creates an etcd client based on the provided config and waits // until etcd server is reachable. It errors out and exits if the server cannot // be reached for a certain amount of time. -func GetAndTestEtcdClient(etcdURL string) (*etcdclient.Client, error) { - etcdServers := []string{etcdURL} - etcdClient := etcdclient.NewClient(etcdServers) +func GetAndTestEtcdClient(etcdClientInfo configapi.EtcdConnectionInfo) (*etcdclient.Client, error) { + var etcdClient *etcdclient.Client + + if len(etcdClientInfo.ClientCert.CertFile) > 0 { + tlsClient, err := etcdclient.NewTLSClient( + etcdClientInfo.URLs, + etcdClientInfo.ClientCert.CertFile, + etcdClientInfo.ClientCert.KeyFile, + etcdClientInfo.CA, + ) + if err != nil { + return nil, err + } + etcdClient = tlsClient + } else if len(etcdClientInfo.CA) > 0 { + etcdClient = etcdclient.NewClient(etcdClientInfo.URLs) + err := etcdClient.AddRootCA(etcdClientInfo.CA) + if err != nil { + return nil, err + } + } else { + etcdClient = etcdclient.NewClient(etcdClientInfo.URLs) + } for i := 0; ; i++ { // TODO: make sure this works with etcd2 (root key may not exist) @@ -65,9 +95,9 @@ func GetAndTestEtcdClient(etcdURL string) (*etcdclient.Client, error) { // newOpenShiftEtcdHelper returns an EtcdHelper for the provided arguments or an error if the version // is incorrect. -func NewOpenShiftEtcdHelper(etcdURL string) (helper tools.EtcdHelper, err error) { +func NewOpenShiftEtcdHelper(etcdClientInfo configapi.EtcdConnectionInfo) (helper tools.EtcdHelper, err error) { // Connect and setup etcd interfaces - client, err := GetAndTestEtcdClient(etcdURL) + client, err := GetAndTestEtcdClient(etcdClientInfo) if err != nil { return tools.EtcdHelper{}, err } diff --git a/pkg/cmd/server/kubernetes/master.go b/pkg/cmd/server/kubernetes/master.go index 7916aa85b162..579534b710ed 100644 --- a/pkg/cmd/server/kubernetes/master.go +++ b/pkg/cmd/server/kubernetes/master.go @@ -44,11 +44,7 @@ func (c *MasterConfig) EnsurePortalFlags() { // endpoints were started (these are format strings that will expect to be sent // a single string value). func (c *MasterConfig) InstallAPI(container *restful.Container) []string { - kubeletClient, err := kclient.NewKubeletClient( - &kclient.KubeletConfig{ - Port: 10250, - }, - ) + kubeletClient, err := kclient.NewKubeletClient(c.KubeletClientConfig) if err != nil { glog.Fatalf("Unable to configure Kubelet client: %v", err) } @@ -123,11 +119,7 @@ func (c *MasterConfig) RunMinionController() { }, } - // TODO: enable this for TLS and make configurable - kubeletClient, err := kclient.NewKubeletClient(&kclient.KubeletConfig{ - Port: 10250, - EnableHttps: false, - }) + kubeletClient, err := kclient.NewKubeletClient(c.KubeletClientConfig) if err != nil { glog.Fatalf("Failure to create kubelet client: %v", err) } diff --git a/pkg/cmd/server/kubernetes/master_config.go b/pkg/cmd/server/kubernetes/master_config.go index be33e069d18d..7b219d150c3a 100644 --- a/pkg/cmd/server/kubernetes/master_config.go +++ b/pkg/cmd/server/kubernetes/master_config.go @@ -29,8 +29,9 @@ type MasterConfig struct { RequestContextMapper kapi.RequestContextMapper - EtcdHelper tools.EtcdHelper - KubeClient *kclient.Client + EtcdHelper tools.EtcdHelper + KubeClient *kclient.Client + KubeletClientConfig *kclient.KubeletConfig Authorizer authorizer.Authorizer AdmissionControl admission.Interface @@ -44,7 +45,7 @@ func BuildKubernetesMasterConfig(options configapi.MasterConfig, requestContextM } // Connect and setup etcd interfaces - etcdClient, err := etcd.GetAndTestEtcdClient(options.EtcdClientInfo.URL) + etcdClient, err := etcd.GetAndTestEtcdClient(options.EtcdClientInfo) if err != nil { return nil, err } @@ -53,6 +54,8 @@ func BuildKubernetesMasterConfig(options configapi.MasterConfig, requestContextM return nil, fmt.Errorf("Error setting up Kubernetes server storage: %v", err) } + kubeletClientConfig := configapi.GetKubeletClientConfig(options) + portalNet := net.IPNet(flagtypes.DefaultIPNet(options.KubernetesMasterConfig.ServicesSubnet)) // in-order list of plug-ins that should intercept admission decisions @@ -77,6 +80,7 @@ func BuildKubernetesMasterConfig(options configapi.MasterConfig, requestContextM RequestContextMapper: requestContextMapper, EtcdHelper: ketcdHelper, KubeClient: kubeClient, + KubeletClientConfig: kubeletClientConfig, Authorizer: apiserver.NewAlwaysAllowAuthorizer(), AdmissionControl: admissionController, SchedulerConfigFile: options.KubernetesMasterConfig.SchedulerConfigFile, diff --git a/pkg/cmd/server/kubernetes/node.go b/pkg/cmd/server/kubernetes/node.go index 8ddc6e3beb17..4ee0157c3760 100644 --- a/pkg/cmd/server/kubernetes/node.go +++ b/pkg/cmd/server/kubernetes/node.go @@ -31,12 +31,6 @@ import ( "github.com/openshift/origin/pkg/service" ) -// NodeScheme is the default scheme for serving information about the node. -const NodeScheme = "http" - -// NodePort is the default Kubelet port for serving information about the node. -const NodePort = 10250 - type commandExecutor interface { LookPath(executable string) (string, error) Run(command string, args ...string) error @@ -174,7 +168,7 @@ func (c *NodeConfig) RunKubelet() { } go util.Forever(func() { - glog.Infof("Started Kubelet for node %s, server at %s", c.NodeHost, c.BindAddress) + glog.Infof("Started Kubelet for node %s, server at %s, tls=%v", c.NodeHost, c.BindAddress, c.TLS) if clusterDNS != nil { glog.Infof(" Kubelet is setting %s as a DNS nameserver for domain %q", clusterDNS, c.ClusterDomain) } @@ -183,9 +177,8 @@ func (c *NodeConfig) RunKubelet() { server.TLSConfig = &tls.Config{ // Change default from SSLv3 to TLSv1.0 (because of POODLE vulnerability) MinVersion: tls.VersionTLS10, - // Populate PeerCertificates in requests, but don't reject connections without certificates - // This allows certificates to be validated by authenticators, while still allowing other auth types - ClientAuth: tls.RequestClientCert, + // RequireAndVerifyClientCert lets us limit requests to ones with a valid client certificate + ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: c.ClientCAs, } glog.Fatal(server.ListenAndServeTLS(c.KubeletCertFile, c.KubeletKeyFile)) diff --git a/pkg/cmd/server/kubernetes/node_config.go b/pkg/cmd/server/kubernetes/node_config.go index cddf23e75ed0..336020b7b193 100644 --- a/pkg/cmd/server/kubernetes/node_config.go +++ b/pkg/cmd/server/kubernetes/node_config.go @@ -9,6 +9,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools" configapi "github.com/openshift/origin/pkg/cmd/server/api" + "github.com/openshift/origin/pkg/cmd/server/crypto" ) // NodeConfig represents the required parameters to start the OpenShift node @@ -38,6 +39,7 @@ type NodeConfig struct { // Whether to enable TLS serving TLS bool + // Enable TLS serving KubeletCertFile string KubeletKeyFile string @@ -65,10 +67,20 @@ func BuildKubernetesNodeConfig(options configapi.NodeConfig) (*NodeConfig, error } } + clientCAs, err := crypto.CertPoolFromFile(options.ServingInfo.ClientCA) + if err != nil { + return nil, err + } + config := &NodeConfig{ NodeHost: options.NodeName, BindAddress: options.ServingInfo.BindAddress, + TLS: configapi.UseTLS(options.ServingInfo), + KubeletCertFile: options.ServingInfo.ServerCert.CertFile, + KubeletKeyFile: options.ServingInfo.ServerCert.KeyFile, + ClientCAs: clientCAs, + ClusterDomain: options.DNSDomain, ClusterDNS: dnsIP, diff --git a/pkg/cmd/server/origin/auth_config.go b/pkg/cmd/server/origin/auth_config.go index 5f0651782add..7be5c62597a7 100644 --- a/pkg/cmd/server/origin/auth_config.go +++ b/pkg/cmd/server/origin/auth_config.go @@ -31,7 +31,7 @@ type AuthConfig struct { } func BuildAuthConfig(options configapi.MasterConfig) (*AuthConfig, error) { - etcdHelper, err := etcd.NewOpenShiftEtcdHelper(options.EtcdClientInfo.URL) + etcdHelper, err := etcd.NewOpenShiftEtcdHelper(options.EtcdClientInfo) if err != nil { return nil, fmt.Errorf("Error setting up server storage: %v", err) } diff --git a/pkg/cmd/server/origin/master.go b/pkg/cmd/server/origin/master.go index bf5d3d184e30..860813b52ab9 100644 --- a/pkg/cmd/server/origin/master.go +++ b/pkg/cmd/server/origin/master.go @@ -129,6 +129,11 @@ func (c *MasterConfig) InstallProtectedAPI(container *restful.Container) []strin glog.Fatalf("OPENSHIFT_DEFAULT_REGISTRY variable is invalid %q: %v", defaultRegistry, err) } + kubeletClient, err := kclient.NewKubeletClient(c.KubeletClientConfig) + if err != nil { + glog.Fatalf("Unable to configure Kubelet client: %v", err) + } + buildEtcd := buildetcd.New(c.EtcdHelper) deployEtcd := deployetcd.New(c.EtcdHelper) routeEtcd := routeetcd.New(c.EtcdHelper) @@ -184,7 +189,7 @@ func (c *MasterConfig) InstallProtectedAPI(container *restful.Container) []strin "builds/clone": buildClone, "buildConfigs": buildconfigregistry.NewREST(buildEtcd), "buildConfigs/instantiate": buildConfigInstantiate, - "buildLogs": buildlogregistry.NewREST(buildEtcd, c.BuildLogClient()), + "buildLogs": buildlogregistry.NewREST(buildEtcd, c.BuildLogClient(), kubeletClient), "images": imageStorage, "imageStreams": imageRepositoryStorage, diff --git a/pkg/cmd/server/origin/master_config.go b/pkg/cmd/server/origin/master_config.go index 45b15ff82426..0419a4cfb44b 100644 --- a/pkg/cmd/server/origin/master_config.go +++ b/pkg/cmd/server/origin/master_config.go @@ -62,7 +62,8 @@ type MasterConfig struct { // a function that returns the appropriate image to use for a named component ImageFor func(component string) string - EtcdHelper tools.EtcdHelper + EtcdHelper tools.EtcdHelper + KubeletClientConfig *kclient.KubeletConfig // ClientCAs will be used to request client certificates in connections to the API. // This CertPool should contain all the CAs that will be used for client certificate verification. @@ -91,7 +92,7 @@ type MasterConfig struct { func BuildMasterConfig(options configapi.MasterConfig) (*MasterConfig, error) { - etcdHelper, err := etcd.NewOpenShiftEtcdHelper(options.EtcdClientInfo.URL) + etcdHelper, err := etcd.NewOpenShiftEtcdHelper(options.EtcdClientInfo) if err != nil { return nil, fmt.Errorf("Error setting up server storage: %v", err) } @@ -125,6 +126,8 @@ func BuildMasterConfig(options configapi.MasterConfig) (*MasterConfig, error) { policyCache := newPolicyCache(etcdHelper) requestContextMapper := kapi.NewRequestContextMapper() + kubeletClientConfig := configapi.GetKubeletClientConfig(options) + // in-order list of plug-ins that should intercept admission decisions (origin only intercepts) admissionControlPluginNames := []string{"AlwaysAdmit"} admissionController := admission.NewFromPlugins(kubeClient, admissionControlPluginNames, "") @@ -145,8 +148,9 @@ func BuildMasterConfig(options configapi.MasterConfig) (*MasterConfig, error) { TLS: configapi.UseTLS(options.ServingInfo), - ImageFor: imageTemplate.ExpandOrDie, - EtcdHelper: etcdHelper, + ImageFor: imageTemplate.ExpandOrDie, + EtcdHelper: etcdHelper, + KubeletClientConfig: kubeletClientConfig, ClientCAs: clientCAs, APIClientCAs: apiClientCAs, diff --git a/pkg/cmd/server/start/config_test.go b/pkg/cmd/server/start/config_test.go index 237464b89f24..74e6499d9634 100644 --- a/pkg/cmd/server/start/config_test.go +++ b/pkg/cmd/server/start/config_test.go @@ -209,8 +209,8 @@ func TestKubernetesAddressExplicit(t *testing.T) { } func TestEtcdAddressDefaulting(t *testing.T) { - expected := "http://example.com:4001" - master := "https://example.com:9012" + expected := "https://example.com:4001" + master := "http://example.com:9012" masterArgs := NewDefaultMasterArgs() masterArgs.MasterAddr.Set(master) @@ -224,6 +224,25 @@ func TestEtcdAddressDefaulting(t *testing.T) { } } +func TestEtcdAddressUseListenScheme(t *testing.T) { + // Scheme from --listen arg + // Host from --master arg + // Port from default + expected := "http://example.com:4001" + + masterArgs := NewDefaultMasterArgs() + masterArgs.MasterAddr.Set("https://example.com:9012") + masterArgs.ListenArg.ListenAddr.Set("http://0.0.0.0:8043") + + actual, err := masterArgs.GetEtcdAddress() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if expected != actual.String() { + t.Fatalf("expected %v, got %v", expected, actual) + } +} + func TestEtcdAddressExplicit(t *testing.T) { expected := "http://external.com:12445" diff --git a/pkg/cmd/server/start/master_args.go b/pkg/cmd/server/start/master_args.go index 30fa435b3ccf..8c7b93aabe42 100644 --- a/pkg/cmd/server/start/master_args.go +++ b/pkg/cmd/server/start/master_args.go @@ -11,6 +11,7 @@ import ( "github.com/golang/glog" "github.com/spf13/pflag" + "github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" @@ -70,7 +71,7 @@ func BindMasterArgs(args *MasterArgs, flags *pflag.FlagSet, prefix string) { func NewDefaultMasterArgs() *MasterArgs { config := &MasterArgs{ MasterAddr: flagtypes.Addr{Value: "localhost:8443", DefaultScheme: "https", DefaultPort: 8443, AllowPrefix: true}.Default(), - EtcdAddr: flagtypes.Addr{Value: "0.0.0.0:4001", DefaultScheme: "http", DefaultPort: 4001}.Default(), + EtcdAddr: flagtypes.Addr{Value: "0.0.0.0:4001", DefaultScheme: "https", DefaultPort: 4001}.Default(), PortalNet: flagtypes.DefaultIPNet("172.30.17.0/24"), MasterPublicAddr: flagtypes.Addr{Value: "localhost:8443", DefaultScheme: "https", DefaultPort: 8443, AllowPrefix: true}.Default(), KubernetesPublicAddr: flagtypes.Addr{Value: "localhost:8443", DefaultScheme: "https", DefaultPort: 8443, AllowPrefix: true}.Default(), @@ -120,15 +121,18 @@ func (args MasterArgs) BuildSerializeableMasterConfig() (*configapi.MasterConfig return nil, err } + builtInEtcd := !args.EtcdAddr.Provided var etcdConfig *configapi.EtcdConfig - if !args.EtcdAddr.Provided { + if builtInEtcd { etcdConfig, err = args.BuildSerializeableEtcdConfig() if err != nil { return nil, err } } + + builtInKubernetes := !args.KubeConnectionArgs.KubernetesAddr.Provided && len(args.KubeConnectionArgs.ClientConfigLoadingRules.ExplicitPath) == 0 var kubernetesMasterConfig *configapi.KubernetesMasterConfig - if !args.KubeConnectionArgs.KubernetesAddr.Provided && len(args.KubeConnectionArgs.ClientConfigLoadingRules.ExplicitPath) == 0 { + if builtInKubernetes { kubernetesMasterConfig, err = args.BuildSerializeableKubeMasterConfig() if err != nil { return nil, err @@ -140,6 +144,10 @@ func (args MasterArgs) BuildSerializeableMasterConfig() (*configapi.MasterConfig return nil, err } + kubeletClientInfo := admin.DefaultMasterKubeletClientCertInfo(args.CertArgs.CertDir) + + etcdClientInfo := admin.DefaultMasterEtcdClientCertInfo(args.CertArgs.CertDir) + config := &configapi.MasterConfig{ ServingInfo: configapi.ServingInfo{ BindAddress: args.ListenArg.ListenAddr.URL.Host, @@ -172,11 +180,12 @@ func (args MasterArgs) BuildSerializeableMasterConfig() (*configapi.MasterConfig KubernetesKubeConfig: admin.DefaultKubeConfigFilename(args.CertArgs.CertDir, "kube-client"), }, - EtcdClientInfo: configapi.RemoteConnectionInfo{ - URL: etcdAddress.String(), - // TODO allow for https etcd - CA: "", - ClientCert: configapi.CertInfo{}, + EtcdClientInfo: configapi.EtcdConnectionInfo{ + URLs: []string{etcdAddress.String()}, + }, + + KubeletClientInfo: configapi.KubeletConnectionInfo{ + Port: ports.KubeletPort, }, PolicyConfig: configapi.PolicyConfig{ @@ -193,10 +202,21 @@ func (args MasterArgs) BuildSerializeableMasterConfig() (*configapi.MasterConfig if args.ListenArg.UseTLS() { config.ServingInfo.ServerCert = admin.DefaultMasterServingCertInfo(args.CertArgs.CertDir) - config.ServingInfo.ClientCA = admin.DefaultRootCAFile(args.CertArgs.CertDir) + config.ServingInfo.ClientCA = admin.DefaultAPIClientCAFile(args.CertArgs.CertDir) config.AssetConfig.ServingInfo.ServerCert = admin.DefaultAssetServingCertInfo(args.CertArgs.CertDir) - config.AssetConfig.ServingInfo.ClientCA = admin.DefaultRootCAFile(args.CertArgs.CertDir) + + // Only set up ca/cert info for kubelet connections if we're self-hosting Kubernetes + if builtInKubernetes { + config.KubeletClientInfo.CA = admin.DefaultRootCAFile(args.CertArgs.CertDir) + config.KubeletClientInfo.ClientCert = kubeletClientInfo.CertLocation + } + + // Only set up ca/cert info for etcd connections if we're self-hosting etcd + if builtInEtcd { + config.EtcdClientInfo.CA = admin.DefaultRootCAFile(args.CertArgs.CertDir) + config.EtcdClientInfo.ClientCert = etcdClientInfo.CertLocation + } } return config, nil @@ -288,16 +308,33 @@ func (args MasterArgs) BuildSerializeableEtcdConfig() (*configapi.EtcdConfig, er return nil, err } + etcdPeerAddr, err := args.GetEtcdPeerAddress() + if err != nil { + return nil, err + } + config := &configapi.EtcdConfig{ ServingInfo: configapi.ServingInfo{ BindAddress: args.GetEtcdBindAddress(), }, - PeerAddress: args.GetEtcdPeerBindAddress(), - MasterAddress: etcdAddr.Host, - StorageDir: args.EtcdDir, + PeerServingInfo: configapi.ServingInfo{ + BindAddress: args.GetEtcdPeerBindAddress(), + }, + Address: etcdAddr.Host, + PeerAddress: etcdPeerAddr.Host, + StorageDir: args.EtcdDir, + } + + if args.ListenArg.UseTLS() { + config.ServingInfo.ServerCert = admin.DefaultEtcdServingCertInfo(args.CertArgs.CertDir) + config.ServingInfo.ClientCA = admin.DefaultEtcdClientCAFile(args.CertArgs.CertDir) + + config.PeerServingInfo.ServerCert = admin.DefaultEtcdServingCertInfo(args.CertArgs.CertDir) + config.PeerServingInfo.ClientCA = admin.DefaultEtcdClientCAFile(args.CertArgs.CertDir) } return config, nil + } // BuildSerializeableKubeMasterConfig creates a fully specified kubernetes master startup configuration based on MasterArgs @@ -398,17 +435,11 @@ func (args MasterArgs) GetMasterAddress() (*url.URL, error) { return args.MasterAddr.URL, nil } - // If the user specifies a bind address, and the master is not provided, use the bind port by default - port := args.MasterAddr.Port - if args.ListenArg.ListenAddr.Provided { - port = args.ListenArg.ListenAddr.Port - } + // Use the bind port by default + port := args.ListenArg.ListenAddr.Port - // If the user specifies a bind address, and the master is not provided, use the bind scheme by default - scheme := args.MasterAddr.URL.Scheme - if args.ListenArg.ListenAddr.Provided { - scheme = args.ListenArg.ListenAddr.URL.Scheme - } + // Use the bind scheme by default + scheme := args.ListenArg.ListenAddr.URL.Scheme addr := "" if ip, err := cmdutil.DefaultLocalIP4(); err == nil { @@ -460,8 +491,29 @@ func (args MasterArgs) GetEtcdAddress() (*url.URL, error) { return nil, err } - etcdAddr := net.JoinHostPort(getHost(*masterAddr), strconv.Itoa(args.EtcdAddr.DefaultPort)) - return url.Parse(args.EtcdAddr.DefaultScheme + "://" + etcdAddr) + return &url.URL{ + // Use the bind scheme by default + Scheme: args.ListenArg.ListenAddr.URL.Scheme, + + Host: net.JoinHostPort(getHost(*masterAddr), strconv.Itoa(args.EtcdAddr.DefaultPort)), + }, nil +} + +func (args MasterArgs) GetEtcdPeerAddress() (*url.URL, error) { + // Derive from the etcd address, on port 7001 + etcdAddress, err := args.GetEtcdAddress() + if err != nil { + return nil, err + } + + host, _, err := net.SplitHostPort(etcdAddress.Host) + if err != nil { + return nil, err + } + + etcdAddress.Host = net.JoinHostPort(host, "7001") + + return etcdAddress, nil } func (args MasterArgs) GetKubernetesPublicAddress() (*url.URL, error) { if args.KubernetesPublicAddr.Provided { diff --git a/pkg/cmd/server/start/node_args.go b/pkg/cmd/server/start/node_args.go index 38dd62d5e53b..0280b5d2233b 100644 --- a/pkg/cmd/server/start/node_args.go +++ b/pkg/cmd/server/start/node_args.go @@ -98,6 +98,7 @@ func (args NodeArgs) BuildSerializeableNodeConfig() (*configapi.NodeConfig, erro if args.ListenArg.UseTLS() { config.ServingInfo.ServerCert = admin.DefaultNodeServingCertInfo(args.CertArgs.CertDir, args.NodeName) + config.ServingInfo.ClientCA = admin.DefaultKubeletClientCAFile(args.CertArgs.CertDir) } return config, nil diff --git a/pkg/cmd/server/start/start_master.go b/pkg/cmd/server/start/start_master.go index f8c9a0136cb3..54bccc22ebff 100644 --- a/pkg/cmd/server/start/start_master.go +++ b/pkg/cmd/server/start/start_master.go @@ -295,18 +295,11 @@ func ReadMasterConfig(filename string) (*configapi.MasterConfig, error) { } func StartMaster(openshiftMasterConfig *configapi.MasterConfig) error { - glog.Infof("Starting an OpenShift master, reachable at %s (etcd: %s)", openshiftMasterConfig.ServingInfo.BindAddress, openshiftMasterConfig.EtcdClientInfo.URL) + glog.Infof("Starting an OpenShift master, reachable at %s (etcd: %v)", openshiftMasterConfig.ServingInfo.BindAddress, openshiftMasterConfig.EtcdClientInfo.URLs) glog.Infof("OpenShift master public address is %s", openshiftMasterConfig.AssetConfig.MasterPublicURL) if openshiftMasterConfig.EtcdConfig != nil { - etcdConfig := &etcd.Config{ - BindAddr: openshiftMasterConfig.EtcdConfig.ServingInfo.BindAddress, - PeerBindAddr: openshiftMasterConfig.EtcdConfig.PeerAddress, - MasterAddr: openshiftMasterConfig.EtcdConfig.MasterAddress, - EtcdDir: openshiftMasterConfig.EtcdConfig.StorageDir, - } - - etcdConfig.Run() + etcd.RunEtcd(openshiftMasterConfig.EtcdConfig) } // Allow privileged containers diff --git a/pkg/cmd/server/start/start_node.go b/pkg/cmd/server/start/start_node.go index feb8a3dac6fa..bca638ba02ac 100644 --- a/pkg/cmd/server/start/start_node.go +++ b/pkg/cmd/server/start/start_node.go @@ -241,6 +241,8 @@ func (o NodeOptions) CreateCerts() error { APIServerURL: masterAddr.String(), APIServerCAFile: getSignerOptions.CertFile, + + NodeClientCAFile: getSignerOptions.CertFile, } if err := createNodeConfigOptions.Validate(nil); err != nil { diff --git a/test/integration/bootstrap_policy_test.go b/test/integration/bootstrap_policy_test.go index 94d3fd94604d..b27b9323e593 100644 --- a/test/integration/bootstrap_policy_test.go +++ b/test/integration/bootstrap_policy_test.go @@ -94,7 +94,7 @@ func TestOverwritePolicyCommand(t *testing.T) { t.Errorf("unexpected error: %v", err) } - etcdHelper, err := etcd.NewOpenShiftEtcdHelper(masterConfig.EtcdClientInfo.URL) + etcdHelper, err := etcd.NewOpenShiftEtcdHelper(masterConfig.EtcdClientInfo) if err != nil { t.Errorf("unexpected error: %v", err) } diff --git a/test/integration/oauth_request_header_test.go b/test/integration/oauth_request_header_test.go index c5bf0b27be78..a5d9df257042 100644 --- a/test/integration/oauth_request_header_test.go +++ b/test/integration/oauth_request_header_test.go @@ -158,7 +158,7 @@ func TestOAuthRequestHeader(t *testing.T) { t.Fatalf("expected unsuccessful token request, got redirected to %v", redirect.String()) } - // Use the server d CA info, with cert info + // Use the server and CA info, with cert info authProxyConfig := anonConfig authProxyConfig.CertData = clientCert authProxyConfig.KeyData = clientKey diff --git a/test/integration/userclient_test.go b/test/integration/userclient_test.go index fa41916b22e7..6a701a21f3e3 100644 --- a/test/integration/userclient_test.go +++ b/test/integration/userclient_test.go @@ -78,7 +78,7 @@ func TestUserInitialization(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - etcdHelper, err := etcd.NewOpenShiftEtcdHelper(masterConfig.EtcdClientInfo.URL) + etcdHelper, err := etcd.NewOpenShiftEtcdHelper(masterConfig.EtcdClientInfo) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/test/util/server.go b/test/util/server.go index ef37278f5a63..340cf91d4b19 100644 --- a/test/util/server.go +++ b/test/util/server.go @@ -152,6 +152,7 @@ func CreateNodeCerts(nodeArgs *start.NodeArgs) error { createNodeConfig.Hostnames = []string{nodeArgs.NodeName} createNodeConfig.ListenAddr = nodeArgs.ListenArg.ListenAddr createNodeConfig.APIServerCAFile = admin.DefaultCertFilename(nodeArgs.CertArgs.CertDir, "ca") + createNodeConfig.NodeClientCAFile = admin.DefaultCertFilename(nodeArgs.CertArgs.CertDir, "ca") if err := createNodeConfig.Validate(nil); err != nil { return err diff --git a/vagrant/provision-master-sdn.sh b/vagrant/provision-master-sdn.sh index 351ee260b918..de75e80ee327 100755 --- a/vagrant/provision-master-sdn.sh +++ b/vagrant/provision-master-sdn.sh @@ -26,7 +26,7 @@ Requires=openshift-master.service After=openshift-master.service [Service] -ExecStart=/usr/bin/openshift-sdn -etcd-endpoints=http://${MASTER_IP}:4001 +ExecStart=/usr/bin/openshift-sdn -etcd-endpoints=https://${MASTER_IP}:4001 -etcd-keyfile=${ETCD_KEYFILE} -etcd-certfile=${ETCD_CERTFILE} -etcd-cafile=${ETCD_CAFILE} [Install] WantedBy=multi-user.target diff --git a/vagrant/provision-master.sh b/vagrant/provision-master.sh index 549e1ba8ce6b..e40046996a52 100755 --- a/vagrant/provision-master.sh +++ b/vagrant/provision-master.sh @@ -30,6 +30,12 @@ pushd /vagrant ./hack/install-etcd.sh popd +# Initialize certificates +echo "Generating certs" +pushd /vagrant + /usr/bin/openshift admin create-all-certs --overwrite=false --master=https://${MASTER_IP}:8443 --hostnames=${MASTER_IP},${MASTER_NAME} --nodes=${node_list} +popd + # Start docker systemctl enable docker.service systemctl start docker.service @@ -55,9 +61,12 @@ systemctl start openshift-master.service # if SDN requires service on master, then set it up if [ "${OPENSHIFT_SDN}" != "ovs-gre" ]; then + export ETCD_CA=/vagrant/openshift.local.certificates/ca/cert.crt + export ETCD_CLIENT_CERT=/vagrant/openshift.local.certificates/master/etcd-client.crt + export ETCD_CLIENT_KEY=/vagrant/openshift.local.certificates/master/etcd-client.key $(dirname $0)/provision-master-sdn.sh $@ fi -# Set up the KUBECONFIG environment variable for use by the client +# Set up the KUBECONFIG environment variable for use by osc echo 'export KUBECONFIG=/vagrant/openshift.local.certificates/admin/.kubeconfig' >> /root/.bash_profile echo 'export KUBECONFIG=/vagrant/openshift.local.certificates/admin/.kubeconfig' >> /home/vagrant/.bash_profile diff --git a/vagrant/provision-minion.sh b/vagrant/provision-minion.sh index a4109a45c340..16d6909e50f9 100755 --- a/vagrant/provision-minion.sh +++ b/vagrant/provision-minion.sh @@ -33,18 +33,20 @@ pushd /vagrant cp _output/local/go/bin/openshift /usr/bin popd +# Copy over the certificates directory +cp -r /vagrant/openshift.local.certificates / +chown -R vagrant.vagrant /openshift.local.certificates + if [ "${OPENSHIFT_SDN}" != "ovs-gre" ]; then + export ETCD_CA=/openshift.local.certificates/ca/cert.crt + export ETCD_CLIENT_CERT=/openshift.local.certificates/master/etcd-client.crt + export ETCD_CLIENT_KEY=/openshift.local.certificates/master/etcd-client.key $(dirname $0)/provision-node-sdn.sh $@ else # Setup default networking between the nodes $(dirname $0)/provision-gre-network.sh $@ fi -# Copy over the certificates directory and modify the kubeconfig file to use the master ip -cp -r /vagrant/openshift.local.certificates / -chown -R vagrant.vagrant /openshift.local.certificates -sed -ie "s/10.0.2.15/${MASTER_IP}/g" /openshift.local.certificates/admin/.kubeconfig - # get the minion name, index is 1-based minion_name=${MINION_NAMES[$MINION_INDEX-1]} # Create systemd service @@ -55,7 +57,7 @@ Requires=docker.service network.service After=network.service [Service] -ExecStart=/usr/bin/openshift start node --kubeconfig=/openshift.local.certificates/node-${minion_name}/.kubeconfig +ExecStart=/usr/bin/openshift start node --kubeconfig=/openshift.local.certificates/node-${minion_name}/.kubeconfig --hostname=${minion_name} Restart=on-failure RestartSec=10s @@ -69,8 +71,8 @@ systemctl enable openshift-node.service systemctl start openshift-node.service # Set up the KUBECONFIG environment variable for use by the client -echo 'export KUBECONFIG=/openshift.local.certificates/node-${minion_name}/.kubeconfig' >> /root/.bash_profile -echo 'export KUBECONFIG=/openshift.local.certificates/node-${minion_name}/.kubeconfig' >> /home/vagrant/.bash_profile +echo 'export KUBECONFIG=/openshift.local.certificates/admin/.kubeconfig' >> /root/.bash_profile +echo 'export KUBECONFIG=/openshift.local.certificates/admin/.kubeconfig' >> /home/vagrant/.bash_profile # Register with the master #curl -X POST -H 'Accept: application/json' -d "{\"kind\":\"Minion\", \"id\":"${MINION_IP}", \"apiVersion\":\"v1beta1\", \"hostIP\":"${MINION_IP}" }" http://${MASTER_IP}:8080/api/v1beta1/minions diff --git a/vagrant/provision-node-sdn.sh b/vagrant/provision-node-sdn.sh index 57e2d4a28bb1..91b073b8ae9b 100755 --- a/vagrant/provision-node-sdn.sh +++ b/vagrant/provision-node-sdn.sh @@ -28,7 +28,7 @@ After=openvswitch.service Before=openshift-node.service [Service] -ExecStart=/usr/bin/openshift-sdn -minion -etcd-endpoints=http://${MASTER_IP}:4001 -public-ip=${MINION_IP} +ExecStart=/usr/bin/openshift-sdn -minion -etcd-endpoints=https://${MASTER_IP}:4001 -public-ip=${MINION_IP} -etcd-keyfile=${ETCD_CLIENT_KEY} -etcd-certfile=${ETCD_CLIENT_CERT} -etcd-cafile=${ETCD_CAFILE} [Install] WantedBy=multi-user.target