Skip to content

Commit

Permalink
Merge pull request #48 from dlorenc/auth
Browse files Browse the repository at this point in the history
Add certificate based authentication to localkube/minikube.
  • Loading branch information
dlorenc committed May 9, 2016
2 parents 380c645 + 5a8e107 commit 488e4dc
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 64 deletions.
9 changes: 7 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ clean:
rm -rf $(BUILD_DIR)

gopath:
rm -rf $(GOPATH)
mkdir -p $(shell dirname $(GOPATH)/src/$(REPOPATH))
ln -s $(shell pwd) $(GOPATH)/src/$(REPOPATH)
ln -s -f $(shell pwd) $(GOPATH)/src/$(REPOPATH)

.PHONY: minikube
minikube: minikube-$(GOOS)-$(GOARCH)
Expand All @@ -46,6 +45,12 @@ localkube-$(GOOS)-$(GOARCH): gopath
integration: minikube
go test -v ./test/integration --tags=integration

localkube-incremental:
GOPATH=/go CGO_ENABLED=1 GOBIN=$(shell pwd)/$(BUILD_DIR) go install ./cmd/localkube

docker/localkube:
docker run -w /go/src/k8s.io/minikube -v $(shell pwd):/go/src/k8s.io/minikube golang:1.6 make localkube-incremental

.PHONY: test
test: gopath
./test.sh
15 changes: 2 additions & 13 deletions cmd/minikube/cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,13 @@ limitations under the License.
package cmd

import (
"io/ioutil"
"log"
"os"
"testing"

"github.com/spf13/cobra"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/minikube/tests"
)

func makeTempDir() string {
tempDir, err := ioutil.TempDir("", "minipath")
if err != nil {
log.Fatal(err)
}
constants.Minipath = tempDir
return tempDir
}

func runCommand(f func(*cobra.Command, []string)) {
cmd := cobra.Command{}
var args []string
Expand All @@ -43,7 +32,7 @@ func runCommand(f func(*cobra.Command, []string)) {

func TestPreRunDirectories(t *testing.T) {
// Make sure we create the required directories.
tempDir := makeTempDir()
tempDir := tests.MakeTempDir()
defer os.RemoveAll(tempDir)

runCommand(RootCmd.PersistentPreRun)
Expand Down
30 changes: 24 additions & 6 deletions cmd/minikube/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ assumes you already have Virtualbox installed.`,
Run: runStart,
}

var (
localkubeURL string
)

func runStart(cmd *cobra.Command, args []string) {

fmt.Println("Starting local Kubernetes cluster...")
Expand All @@ -48,7 +52,11 @@ func runStart(cmd *cobra.Command, args []string) {
os.Exit(1)
}

if err := cluster.StartCluster(host); err != nil {
config := cluster.KubernetesConfig{
LocalkubeURL: localkubeURL,
}

if err := cluster.StartCluster(host, config); err != nil {
log.Println("Error starting cluster: ", err)
os.Exit(1)
}
Expand All @@ -57,13 +65,23 @@ func runStart(cmd *cobra.Command, args []string) {
if err != nil {
log.Println("Error connecting to cluster: ", err)
}
kubeHost = strings.Replace(kubeHost, "tcp://", "http://", -1)
kubeHost = strings.Replace(kubeHost, ":2376", ":8080", -1)
log.Printf("Kubernetes is available at %s.\n", kubeHost)
log.Println("Run this command to use the cluster: ")
log.Printf("kubectl config set-cluster minikube --insecure-skip-tls-verify=true --server=%s\n", kubeHost)
kubeHost = strings.Replace(kubeHost, "tcp://", "https://", -1)
kubeHost = strings.Replace(kubeHost, ":2376", ":443", -1)
fmt.Printf("Kubernetes is available at %s.\n", kubeHost)
fmt.Println("Run this command to use the cluster: ")
fmt.Printf("kubectl config set-cluster minikube --server=%s --certificate-authority=$HOME/.minikube/ca.crt\n", kubeHost)
fmt.Println("kubectl config set-credentials minikube --client-certificate=$HOME/.minikube/kubecfg.crt --client-key=$HOME/.minikube/kubecfg.key")
fmt.Println("kubectl config set-context minikube --cluster=minikube --user=minikube")
fmt.Println("kubectl config use-context minikube")

if err := cluster.GetCreds(host); err != nil {
log.Println("Error configuring authentication: ", err)
os.Exit(1)
}
}

func init() {
startCmd.Flags().StringVarP(&localkubeURL, "localkube-url", "", "https://storage.googleapis.com/tinykube/localkube", "Location of the localkube binary")
startCmd.Flags().MarkHidden("localkube-url")
RootCmd.AddCommand(startCmd)
}
17 changes: 14 additions & 3 deletions pkg/localkube/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"net"
"os"
"path/filepath"
"strings"
"time"

Expand All @@ -29,9 +30,12 @@ import (
)

const (
APIServerName = "apiserver"
APIServerHost = "0.0.0.0"
APIServerPort = 8080
APIServerName = "apiserver"
APIServerHost = "127.0.0.1"
APIServerPort = 8080
APIServerSecureHost = "0.0.0.0"
APIServerSecurePort = 443
certPath = "/srv/kubernetes/certs/"
)

var (
Expand Down Expand Up @@ -62,9 +66,16 @@ func StartAPIServer() {
config := options.NewAPIServer()

// use host/port from vars
config.BindAddress = net.ParseIP(APIServerSecureHost)
config.SecurePort = APIServerSecurePort
config.InsecureBindAddress = net.ParseIP(APIServerHost)
config.InsecurePort = APIServerPort

config.ClientCAFile = filepath.Join(certPath, "ca.crt")
config.TLSCertFile = filepath.Join(certPath, "kubernetes-master.crt")
config.TLSPrivateKeyFile = filepath.Join(certPath, "kubernetes-master.key")
config.AdmissionControl = "NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota"

// use localkube etcd
config.EtcdConfig = etcdstorage.EtcdConfig{
ServerList: KubeEtcdClientURLs,
Expand Down
2 changes: 2 additions & 0 deletions pkg/localkube/controller-manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package localkube

import (
"os"
"path/filepath"
"time"

controllerManager "k8s.io/kubernetes/cmd/kube-controller-manager/app"
Expand Down Expand Up @@ -50,6 +51,7 @@ func StartControllerManagerServer() {
config.DeletingPodsQps = 0.1
config.DeletingPodsBurst = 10
config.EnableProfiling = true
config.ServiceAccountKeyFile = filepath.Join(certPath, "kubernetes-master.key")

fn := func() error {
return controllerManager.Run(config)
Expand Down
5 changes: 1 addition & 4 deletions pkg/localkube/kubelet.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ const (
)

var (
WeaveProxySock = "unix:///var/run/weave/weave.sock"
KubeletStop chan struct{}
KubeletStop chan struct{}
)

func NewKubeletServer(clusterDomain, clusterDNS string, containerized bool) Server {
Expand All @@ -50,9 +49,7 @@ func StartKubeletServer(clusterDomain, clusterDNS string, containerized bool) fu
// master details
config.APIServerList = []string{APIServerURL}

// Docker
config.Containerized = containerized
config.DockerEndpoint = WeaveProxySock

// Networking
config.ClusterDomain = clusterDomain
Expand Down
62 changes: 37 additions & 25 deletions pkg/minikube/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ package cluster
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"path/filepath"
"strings"
"time"

Expand All @@ -30,6 +32,14 @@ import (
"k8s.io/minikube/pkg/minikube/constants"
)

const (
remotePath = "/srv/kubernetes/certs"
)

var (
certs = []string{"ca.crt", "kubecfg.key", "kubecfg.crt"}
)

// StartHost starts a host VM.
func StartHost(api libmachine.API) (*host.Host, error) {
if exists, err := api.Exists(constants.MachineName); err != nil {
Expand Down Expand Up @@ -128,35 +138,37 @@ type sshAble interface {
RunSSHCommand(string) (string, error)
}

// StartCluster starts as k8s cluster on the specified Host.
func StartCluster(h sshAble) error {
for _, cmd := range []string{
// Download and install weave, if it doesn't exist.
`if [ ! -e /usr/local/bin/weave ]; then
sudo curl -L git.io/weave -o /usr/local/bin/weave
sudo chmod a+x /usr/local/bin/weave;
fi`,
// Download and install localkube, if it doesn't exist yet.
`if [ ! -e /usr/local/bin/localkube ];
then
sudo curl -L https://github.com/redspread/localkube/releases/download/v1.2.1-v1/localkube-linux -o /usr/local/bin/localkube
sudo chmod a+x /usr/local/bin/localkube;
fi`,
// Start weave.
"weave launch-router",
"weave launch-proxy --without-dns --rewrite-inspect",
"weave expose -h \"localkube.weave.local\"",
// Localkube assumes containerized kubelet, which looks at /rootfs.
"if [ ! -e /rootfs ]; then sudo ln -s / /rootfs; fi",
// Run with nohup so it stays up. Redirect logs to useful places.
"PATH=/usr/local/sbin:$PATH nohup sudo /usr/local/bin/localkube start > /var/log/localkube.out 2> /var/log/localkube.err < /dev/null &"} {
output, err := h.RunSSHCommand(cmd)
log.Println(output)
// KubernetesConfig contains the parameters used to start a cluster.
type KubernetesConfig struct {
LocalkubeURL string
}

// StartCluster starts a k8s cluster on the specified Host.
func StartCluster(h sshAble, config KubernetesConfig) error {
output, err := h.RunSSHCommand(getStartCommand(config.LocalkubeURL))
log.Println(output)
if err != nil {
return err
}

return nil
}

// GetCreds gets the generated credentials required to talk to the APIServer.
func GetCreds(h sshAble) error {
localPath := constants.Minipath

for _, cert := range certs {
remoteCertPath := filepath.Join(remotePath, cert)
localCertPath := filepath.Join(localPath, cert)
data, err := h.RunSSHCommand(fmt.Sprintf("cat %s", remoteCertPath))
if err != nil {
return err
}
if err := ioutil.WriteFile(localCertPath, []byte(data), 0644); err != nil {
return err
}
}

return nil
}

Expand Down
80 changes: 69 additions & 11 deletions pkg/minikube/cluster/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ package cluster

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
"testing"

Expand Down Expand Up @@ -57,32 +61,36 @@ func TestCreateHost(t *testing.T) {
}
}

// Mock Host used for testing. When commands are run, the output from CommandOutput
// is used, if present. Then the output from Error is used, if present. Finally,
// "", nil is returned.
type mockHost struct {
Commands []string
CommandOutput map[string]string
Error string
}

func (m mockHost) RunSSHCommand(cmd string) (string, error) {
m.Commands = append(m.Commands, cmd)
output, ok := m.CommandOutput[cmd]
if ok {
return output, nil
}
if m.Error != "" {
return "", fmt.Errorf(m.Error)
}
return "", nil
}

func TestStartCluster(t *testing.T) {
h := mockHost{}
err := StartCluster(h)
err := StartCluster(h, KubernetesConfig{})
if err != nil {
t.Fatalf("Error starting cluster: %s", err)
}
}

type mockHostError struct{}

func (m mockHostError) RunSSHCommand(cmd string) (string, error) {
return "", fmt.Errorf("Error calling command: %s", cmd)
}

func TestStartClusterError(t *testing.T) {
h := mockHostError{}
err := StartCluster(h)
h := mockHost{Error: "error"}
err := StartCluster(h, KubernetesConfig{})
if err == nil {
t.Fatal("Error not thrown starting cluster.")
}
Expand Down Expand Up @@ -271,3 +279,53 @@ func TestGetHostStatus(t *testing.T) {
StopHost(api)
checkState(state.Stopped.String())
}

func TestGetCreds(t *testing.T) {
m := make(map[string]string)
for _, cert := range certs {
m[fmt.Sprintf("cat %s/%s", remotePath, cert)] = cert
}

h := mockHost{CommandOutput: m}

tempDir := tests.MakeTempDir()
defer os.RemoveAll(tempDir)

if err := GetCreds(h); err != nil {
t.Fatalf("Error starting cluster: %s", err)
}

for _, cert := range certs {
// Files should be created with contents matching the output.
certPath := filepath.Join(tempDir, cert)
contents, err := ioutil.ReadFile(certPath)
if err != nil {
t.Fatalf("Error %s reading file: %s", err, certPath)
}
if !reflect.DeepEqual(contents, []byte(cert)) {
t.Fatalf("Contents of file are: %s, should be %s", contents, cert)
}
}
}

func TestGetCredsError(t *testing.T) {
h := mockHost{
Error: "error getting creds",
}
tempDir := tests.MakeTempDir()
defer os.RemoveAll(tempDir)

if err := GetCreds(h); err == nil {
t.Fatalf("Error should have been thrown, but was not.")
}

// No files should have been created.
for _, cert := range certs {
certPath := filepath.Join(tempDir, cert)
_, err := os.Stat(certPath)
if !os.IsNotExist(err) {
t.Fatalf("File %s should not exist.", certPath)
}
}

}
Loading

0 comments on commit 488e4dc

Please sign in to comment.