Merge pull request #189 from vaikas-google/master

Add support for k8s secrets backed credentials
pull/192/head
Brendan Melville 9 years ago
commit b2ebb24c08

@ -167,6 +167,14 @@ type KubernetesObject struct {
Spec map[string]interface{} `json:"spec"`
}
// KubernetesSecret represents a Kubernetes secret
type KubernetesSecret struct {
Kind string `json:"kind"`
ApiVersion string `json:"apiVersion"`
Metadata map[string]string `json:"metadata"`
Data map[string]string `json:"data,omitempty"`
}
// Repository related types
type BasicAuthCredential struct {
Username string `json:"username"`
@ -186,7 +194,7 @@ type RegistryCredential struct {
type Registry struct {
Name string `json:"name,omitempty"` // Friendly name for the registry
Type RegistryType `json:"type,omitempty"` // Technology implementing the registry
URL string `json:"name,omitempty"` // URL to the root of the registry
URL string `json:"url,omitempty"` // URL to the root of the registry
Format RegistryFormat `json:"format,omitempty"` // Format of the registry
CredentialName string `json:"credentialname,omitempty"` // Name of the credential to use
}

@ -21,8 +21,6 @@ import (
"github.com/kubernetes/deployment-manager/common"
"github.com/kubernetes/deployment-manager/expandybird/expander"
"github.com/kubernetes/deployment-manager/registry"
"github.com/kubernetes/deployment-manager/util"
"archive/tar"
"bytes"
@ -35,8 +33,6 @@ import (
"net/http"
"net/url"
"os"
"path"
"regexp"
"strconv"
"strings"
"time"
@ -46,10 +42,10 @@ var (
deployment_name = flag.String("name", "", "Name of deployment, used for deploy and update commands (defaults to template name)")
stdin = flag.Bool("stdin", false, "Reads a configuration from the standard input")
properties = flag.String("properties", "", "Properties to use when deploying a template (e.g., --properties k1=v1,k2=v2)")
template_registry = flag.String("registry", "github.com/kubernetes/application-dm-templates", "Registry (github.com/owner/repo)")
template_registry = flag.String("registry", "application-dm-templates", "Registry name")
service = flag.String("service", "http://localhost:8001/api/v1/proxy/namespaces/dm/services/manager-service:manager", "URL for deployment manager")
binary = flag.String("binary", "../expandybird/expansion/expansion.py", "Path to template expansion binary")
timeout = flag.Int("timeout", 10, "Time in seconds to wait for response")
timeout = flag.Int("timeout", 20, "Time in seconds to wait for response")
regex_string = flag.String("regex", "", "Regular expression to filter the templates listed in a template registry")
username = flag.String("username", "", "Github user name that overrides GITHUB_USERNAME environment variable")
password = flag.String("password", "", "Github password that overrides GITHUB_PASSWORD environment variable")
@ -67,6 +63,7 @@ var commands = []string{
"deployed-types \t\t Lists the types deployed in the cluster",
"deployed-instances \t Lists the instances of the named type deployed in the cluster",
"templates \t\t Lists the templates in a given template registry",
"registries \t\t Lists the registries available",
"describe \t\t Describes the named template in a given template registry",
}
@ -87,45 +84,6 @@ var usage = func() {
panic("\n")
}
// TODO(jackgr): Move all registry related operations to the server side.
var registryProvider registry.RegistryProvider
func getRegistryProvider() registry.RegistryProvider {
if registryProvider == nil {
rs := registry.NewInmemRegistryService()
_, err := rs.GetByURL(*template_registry)
if err != nil {
r := newRegistry(*template_registry)
if err := rs.Create(r); err != nil {
panic(fmt.Errorf("cannot configure registry at %s: %s", r.URL, err))
}
}
cp := registry.NewInmemCredentialProvider()
credential := getGithubCredential()
if credential != nil {
if err := cp.SetCredential("default", credential); err != nil {
panic(fmt.Errorf("cannot set credential at %s: %s", "default", err))
}
}
registryProvider = registry.NewRegistryProvider(rs, nil, cp)
}
return registryProvider
}
func newRegistry(URL string) *common.Registry {
tFormat := fmt.Sprintf("%s;%s", common.VersionedRegistry, common.CollectionRegistry)
return &common.Registry{
Name: util.TrimURLScheme(URL),
Type: common.GithubRegistryType,
URL: URL,
Format: common.RegistryFormat(tFormat),
CredentialName: "default",
}
}
func getGithubCredential() *common.RegistryCredential {
*apitoken = strings.TrimSpace(*apitoken)
if *apitoken == "" {
@ -160,16 +118,6 @@ func getGithubCredential() *common.RegistryCredential {
return nil
}
func getGithubRegistry() registry.Registry {
provider := getRegistryProvider()
git, err := provider.GetRegistryByShortURL(*template_registry)
if err != nil {
panic(fmt.Errorf("cannot open registry %s: %s", *template_registry, err))
}
return git
}
func main() {
defer func() {
result := recover()
@ -192,32 +140,8 @@ func execute() {
switch args[0] {
case "templates":
var regex *regexp.Regexp
if *regex_string != "" {
var err error
regex, err = regexp.Compile(*regex_string)
if err != nil {
panic(fmt.Errorf("cannot compile regular expression %s: %s", *regex_string, err))
}
}
git := getGithubRegistry()
types, err := git.ListTypes(regex)
if err != nil {
panic(fmt.Errorf("cannot list templates in registry %s: %s", *template_registry, err))
}
for _, t := range types {
fmt.Printf("%s\n", t.String())
urls, err := git.GetDownloadURLs(t)
if err != nil {
panic(fmt.Errorf("cannot get download urls for %s: %s", t, err))
}
for _, downloadURL := range urls {
fmt.Printf("\t%s\n", downloadURL)
}
}
path := fmt.Sprintf("registries/%s/types", args[1])
callService(path, "GET", "list templates", nil)
case "describe":
describeType(args)
case "expand":
@ -293,6 +217,8 @@ func execute() {
path := fmt.Sprintf("types/%s/instances", url.QueryEscape(tUrl))
action := fmt.Sprintf("list deployed instances of type %s", tUrl)
callService(path, "GET", action, nil)
case "registries":
callService("registries", "GET", "list registries", nil)
default:
usage()
}
@ -361,16 +287,15 @@ func describeType(args []string) {
fmt.Println(callHttp(schemaUrl, "GET", "get schema for type ("+tUrls[0]+")", nil))
}
// getDownloadURLs returns URLs or empty list if a primitive type.
// getDownloadURLs returns URLs for a type in the given registry
func getDownloadURLs(tName string) []string {
qName := path.Join(*template_registry, tName)
provider := getRegistryProvider()
result, err := registry.GetDownloadURLs(provider, qName)
if err != nil {
panic(fmt.Errorf("cannot get URLs for %s: %s\n", tName, err))
path := fmt.Sprintf("%s/registries/%s/types/%s", *service, *template_registry, url.QueryEscape(tName))
resp := callHttp(path, "GET", "get download urls", nil)
u := []string{}
if err := json.Unmarshal([]byte(resp), &u); err != nil {
panic(fmt.Errorf("Failed to parse JSON response from service: %s", resp))
}
return result
return u
}
func loadTemplate(args []string) *common.Template {
@ -408,13 +333,13 @@ func loadTemplate(args []string) *common.Template {
// See if the first argument is a local file. It could either be a type, or it could be a configuration. If
// it's a local file, it's configuration.
if _, err := os.Stat(args[1]); err == nil {
template, err = expander.NewTemplateFromFileNames(args[1], args[2:])
} else {
if t, err := registry.ParseType(args[1]); err == nil {
template = buildTemplateFromType(t)
if len(args) > 2 {
template, err = expander.NewTemplateFromFileNames(args[1], args[2:])
} else {
template, err = expander.NewTemplateFromRootTemplate(args[1])
}
} else {
template = buildTemplateFromType(args[1])
}
if err != nil {
@ -430,7 +355,7 @@ func loadTemplate(args []string) *common.Template {
return template
}
func buildTemplateFromType(t registry.Type) *common.Template {
func buildTemplateFromType(t string) *common.Template {
props := make(map[string]interface{})
if *properties != "" {
plist := strings.Split(*properties, ",")
@ -452,11 +377,11 @@ func buildTemplateFromType(t registry.Type) *common.Template {
}
// Name the deployment after the type name.
name := fmt.Sprintf("%s:%s", t.Name, t.GetVersion())
name := t
config := common.Configuration{Resources: []*common.Resource{&common.Resource{
Name: name,
Type: getDownloadURLs(t.String())[0],
Type: getDownloadURLs(t)[0],
Properties: props,
}}}

@ -38,7 +38,7 @@ if [[ -z $MANAGER ]] ; then
exit 1
fi
pkill -f $MANAGER
nohup $MANAGER > $LOGDIR/manager.log 2>&1 --port=8080 --expanderURL=http://localhost:8081 --deployerURL=http://localhost:8082 &
nohup $MANAGER > $LOGDIR/manager.log 2>&1 --port=8080 --kubectl=$KUBECTL --expanderURL=http://localhost:8081 --deployerURL=http://localhost:8082 &
echo
echo "Creating dm namespace..."

@ -15,6 +15,17 @@
FROM golang:1.4
MAINTAINER Jack Greenfield <jackgr@google.com>
RUN apt-get update \
&& apt-get autoremove -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
WORKDIR /usr/local/bin
ENV KUBE_VERSION v1.0.5
RUN curl -fsSL -o kubectl https://storage.googleapis.com/kubernetes-release/release/$KUBE_VERSION/bin/linux/amd64/kubectl \
&& chmod +x kubectl
RUN mkdir -p "$GOPATH/src/github.com" && chmod 777 "$GOPATH/src/github.com"
WORKDIR "$GOPATH/src/github.com"
@ -37,4 +48,4 @@ RUN go-wrapper install github.com/kubernetes/deployment-manager/manager/...
EXPOSE 8080
ENTRYPOINT ["bin/manager"]
ENTRYPOINT ["bin/manager", "--kubectl=/usr/local/bin/kubectl"]

@ -61,12 +61,13 @@ var deployments = []Route{
}
var (
maxLength = flag.Int64("maxLength", 1024, "The maximum length (KB) of a template.")
expanderName = flag.String("expander", "expandybird-service", "The DNS name of the expander service.")
expanderURL = flag.String("expanderURL", "", "The URL for the expander service.")
deployerName = flag.String("deployer", "resourcifier-service", "The DNS name of the deployer service.")
deployerURL = flag.String("deployerURL", "", "The URL for the deployer service.")
credentialFile = flag.String("credentialFile", "", "Local file to use for credentials.")
maxLength = flag.Int64("maxLength", 1024, "The maximum length (KB) of a template.")
expanderName = flag.String("expander", "expandybird-service", "The DNS name of the expander service.")
expanderURL = flag.String("expanderURL", "", "The URL for the expander service.")
deployerName = flag.String("deployer", "resourcifier-service", "The DNS name of the deployer service.")
deployerURL = flag.String("deployerURL", "", "The URL for the deployer service.")
credentialFile = flag.String("credentialFile", "", "Local file to use for credentials.")
credentialSecrets = flag.Bool("credentialSecrets", true, "Use secrets for credentials.")
)
var backend manager.Manager
@ -79,11 +80,16 @@ func init() {
routes = append(routes, deployments...)
var credentialProvider common.CredentialProvider
if *credentialFile != "" {
if *credentialSecrets {
panic(fmt.Errorf("Both credentialFile and credentialSecrets are set"))
}
var err error
credentialProvider, err = registry.NewFilebasedCredentialProvider(*credentialFile)
if err != nil {
panic(fmt.Errorf("cannot create credential provider %s: %s", *credentialFile, err))
}
} else if *credentialSecrets {
credentialProvider = registry.NewSecretsCredentialProvider()
} else {
credentialProvider = registry.NewInmemCredentialProvider()
}
@ -424,7 +430,11 @@ func getDownloadURLsHandlerFunc(w http.ResponseWriter, r *http.Request) {
return
}
util.LogHandlerExitWithJSON(handler, w, c, http.StatusOK)
urls := []string{}
for _, u := range c {
urls = append(urls, u.String())
}
util.LogHandlerExitWithJSON(handler, w, urls, http.StatusOK)
}
func getCredential(w http.ResponseWriter, r *http.Request, handler string) *common.RegistryCredential {

@ -6,7 +6,7 @@ you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -43,6 +43,12 @@ var routes = []Route{}
// port to listen on
var port = flag.Int("port", 8080, "The port to listen on")
func init() {
if !flag.Parsed() {
flag.Parse()
}
}
func main() {
if !flag.Parsed() {
flag.Parse()

@ -23,13 +23,7 @@ import (
"github.com/ghodss/yaml"
"github.com/kubernetes/deployment-manager/common"
/*
"net/url"
"regexp"
"strings"
"sync"
*/)
)
// CredentialProvider provides credentials for registries.
type FilebasedCredentialProvider struct {

@ -124,7 +124,6 @@ func (g GithubPackageRegistry) GetDownloadURLs(t Type) ([]*url.URL, error) {
}
}
}
return downloadURLs, nil
}

@ -59,7 +59,7 @@ type GithubRegistry interface {
type Type struct {
Collection string
Name string
version SemVer
Version SemVer
}
// NewType initializes a type
@ -98,7 +98,7 @@ func (t Type) String() string {
// GetVersion returns the type version with the letter "v" prepended.
func (t Type) GetVersion() string {
var result string
version := t.version.String()
version := t.Version.String()
if version != "0" {
result = "v" + version
}
@ -115,7 +115,7 @@ func (t *Type) SetVersion(version string) error {
return err
}
t.version = s
t.Version = s
return nil
}

@ -0,0 +1,127 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package registry
import (
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"log"
"github.com/ghodss/yaml"
"github.com/kubernetes/deployment-manager/common"
"github.com/kubernetes/deployment-manager/util"
)
var (
kubePath = flag.String("kubectl", "./kubectl", "The path to the kubectl binary.")
kubeService = flag.String("service", "", "The DNS name of the kubernetes service.")
kubeServer = flag.String("server", "", "The IP address and optional port of the kubernetes master.")
kubeInsecure = flag.Bool("insecure-skip-tls-verify", false, "Do not check the server's certificate for validity.")
kubeConfig = flag.String("config", "", "Path to a kubeconfig file.")
kubeCertAuth = flag.String("certificate-authority", "", "Path to a file for the certificate authority.")
kubeClientCert = flag.String("client-certificate", "", "Path to a client certificate file.")
kubeClientKey = flag.String("client-key", "", "Path to a client key file.")
kubeToken = flag.String("token", "", "A service account token.")
kubeUsername = flag.String("username", "", "The username to use for basic auth.")
kubePassword = flag.String("password", "", "The password to use for basic auth.")
)
var kubernetesConfig *util.KubernetesConfig
const secretType = "Secret"
// CredentialProvider provides credentials for registries.
type SecretsCredentialProvider struct {
// Actual object that talks to secrets service.
k util.Kubernetes
}
func NewSecretsCredentialProvider() common.CredentialProvider {
kubernetesConfig := &util.KubernetesConfig{
KubePath: *kubePath,
KubeService: *kubeService,
KubeServer: *kubeServer,
KubeInsecure: *kubeInsecure,
KubeConfig: *kubeConfig,
KubeCertAuth: *kubeCertAuth,
KubeClientCert: *kubeClientCert,
KubeClientKey: *kubeClientKey,
KubeToken: *kubeToken,
KubeUsername: *kubeUsername,
KubePassword: *kubePassword,
}
return &SecretsCredentialProvider{util.NewKubernetesKubectl(kubernetesConfig)}
}
func parseCredential(credential string) (*common.RegistryCredential, error) {
var c common.KubernetesSecret
if err := json.Unmarshal([]byte(credential), &c); err != nil {
return nil, fmt.Errorf("cannot unmarshal credential '%s' (%#v)", credential, err)
}
d, err := base64.StdEncoding.DecodeString(c.Data["credential"])
if err != nil {
return nil, fmt.Errorf("cannot unmarshal credential '%s' (%#v)", c, err)
}
// And then finally unmarshal it from yaml to common.RegistryCredential
r := &common.RegistryCredential{}
if err := yaml.Unmarshal(d, &r); err != nil {
return nil, fmt.Errorf("cannot unmarshal credential %s (%#v)", c, err)
}
return r, nil
}
func (scp *SecretsCredentialProvider) GetCredential(name string) (*common.RegistryCredential, error) {
o, err := scp.k.Get(name, secretType)
if err != nil {
return nil, err
}
return parseCredential(o)
}
func (scp *SecretsCredentialProvider) SetCredential(name string, credential *common.RegistryCredential) error {
// Marshal the credential & base64 encode it.
b, err := yaml.Marshal(credential)
if err != nil {
log.Printf("yaml marshal failed for credential: %s: %v", name, err)
return err
}
enc := base64.StdEncoding.EncodeToString(b)
// Then create a kubernetes object out of it
metadata := make(map[string]string)
metadata["name"] = name
data := make(map[string]string)
data["credential"] = enc
obj := &common.KubernetesSecret{
Kind: secretType,
ApiVersion: "v1",
Metadata: metadata,
Data: data,
}
ko, err := yaml.Marshal(obj)
if err != nil {
log.Printf("yaml marshal failed for kubernetes object: %s: %v", name, err)
return err
}
log.Printf("Calling with: %s", string(ko))
o, err := scp.k.Create(string(ko))
log.Printf("Create returned: %s", o)
return err
}

@ -0,0 +1,41 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
// KubernetesConfiguration defines the configuration options for talking to Kubernetes master
type KubernetesConfig struct {
KubePath string // The path to kubectl binary
KubeService string // DNS name of the kubernetes service
KubeServer string // The IP address and optional port of the kubernetes master
KubeInsecure bool // Do not check the server's certificate for validity
KubeConfig string // Path to a kubeconfig file
KubeCertAuth string // Path to a file for the certificate authority
KubeClientCert string // Path to a client certificate file
KubeClientKey string // Path to a client key file
KubeToken string // A service account token
KubeUsername string // The username to use for basic auth
KubePassword string // The password to use for basic auth
}
// Kubernetes defines the interface for talking to Kubernetes. Currently the
// only implementation is through kubectl, but eventually this could be done
// via direct API calls.
type Kubernetes interface {
Get(name string, resourceType string) (string, error)
Create(resource string) (string, error)
Delete(name string, resourceType string) (string, error)
}

@ -0,0 +1,135 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"bytes"
"fmt"
"log"
"net"
"os"
"os/exec"
)
// KubernetesKubectl implements the interface for talking to Kubernetes by wrapping calls
// via kubectl.
type KubernetesKubectl struct {
KubePath string
// Base level arguments to kubectl. User commands/arguments get appended to this.
Arguments []string
}
func NewKubernetesKubectl(config *KubernetesConfig) Kubernetes {
if config.KubePath == "" {
log.Fatalf("kubectl path cannot be empty")
}
// If a configuration file is specified, then it will provide the server
// address and credentials. If not, then we check for the server address
// and credentials as individual flags.
var args []string
if config.KubeConfig != "" {
config.KubeConfig = os.ExpandEnv(config.KubeConfig)
args = append(args, fmt.Sprintf("--kubeconfig=%s", config.KubeConfig))
} else {
if config.KubeServer != "" {
args = append(args, fmt.Sprintf("--server=https://%s", config.KubeServer))
} else if config.KubeService != "" {
addrs, err := net.LookupHost(config.KubeService)
if err != nil || len(addrs) < 1 {
log.Fatalf("cannot resolve DNS name: %v", config.KubeService)
}
args = append(args, fmt.Sprintf("--server=https://%s", addrs[0]))
}
if config.KubeInsecure {
args = append(args, fmt.Sprintf("--insecure-skip-tls-verify=%s", config.KubeInsecure))
} else {
if config.KubeCertAuth != "" {
args = append(args, fmt.Sprintf("--certificate-authority=%s", config.KubeCertAuth))
if config.KubeClientCert == "" {
args = append(args, fmt.Sprintf("--client-certificate=%s", config.KubeClientCert))
}
if config.KubeClientKey == "" {
args = append(args, fmt.Sprintf("--client-key=%s", config.KubeClientKey))
}
}
}
if config.KubeToken != "" {
args = append(args, fmt.Sprintf("--token=%s", config.KubeToken))
} else {
if config.KubeUsername != "" {
args = append(args, fmt.Sprintf("--username=%s", config.KubeUsername))
}
if config.KubePassword != "" {
args = append(args, fmt.Sprintf("--password=%s", config.KubePassword))
}
}
}
return &KubernetesKubectl{config.KubePath, args}
}
func (k *KubernetesKubectl) Get(name string, resourceType string) (string, error) {
// Specify output as json rather than human readable for easier machine parsing
args := []string{"get",
"-o",
"json",
resourceType,
name}
return k.execute(args, "")
}
func (k *KubernetesKubectl) Create(resource string) (string, error) {
args := []string{"create"}
return k.execute(args, resource)
}
func (k *KubernetesKubectl) Delete(name string, resourceType string) (string, error) {
args := []string{"delete",
resourceType,
name}
return k.execute(args, "")
}
func (k *KubernetesKubectl) execute(args []string, input string) (string, error) {
if len(input) > 0 {
args = append(args, "-f", "-")
}
// Tack on the common arguments to the end of the command line
args = append(args, k.Arguments...)
cmd := exec.Command(k.KubePath, args...)
cmd.Stdin = bytes.NewBuffer([]byte(input))
// Combine stdout and stderr into a single dynamically resized buffer
combined := &bytes.Buffer{}
cmd.Stdout = combined
cmd.Stderr = combined
if err := cmd.Start(); err != nil {
log.Printf("cannot start kubectl %#v", err)
return combined.String(), err
}
if err := cmd.Wait(); err != nil {
log.Printf("kubectl failed: %#v", err)
return combined.String(), err
}
return combined.String(), nil
}
Loading…
Cancel
Save