Skip to content

Commit 34ff500

Browse files
committed
Merge pull request #338 from helgi/k8s_certs
feat(certs): add support for wildcard certificates and domains with new cert cli commands
2 parents 72b7367 + 5662705 commit 34ff500

44 files changed

Lines changed: 2449 additions & 383 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ install:
3535
- make prep-bintray-json
3636
script:
3737
- make test
38-
- make -C client/ build test
38+
- make -C client/ bootstrap build test
3939
- mv client/deis client/deis-linux-amd64
4040
- GOOS=darwin GOARCH=amd64 make -C client build
4141
- mv client/deis client/deis-darwin-amd64

client/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export GO15VENDOREXPERIMENT=1
33
# the filepath to this repository, relative to $GOPATH/src
44
repo_path = github.com/deis/workflow/client
55

6-
DEV_ENV_IMAGE := quay.io/deis/go-dev:0.4.0
6+
DEV_ENV_IMAGE := quay.io/deis/go-dev:0.5.0
77
DEV_ENV_WORK_DIR := /go/src/${repo_path}
88
DEV_ENV_PREFIX := docker run --rm -e GO15VENDOREXPERIMENT=1 -e CGO_ENABLED=0 -v ${CURDIR}:${DEV_ENV_WORK_DIR} -w ${DEV_ENV_WORK_DIR}
99
DEV_ENV_CMD := ${DEV_ENV_PREFIX} ${DEV_ENV_IMAGE}

client/cmd/certs.go

Lines changed: 141 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package cmd
33
import (
44
"fmt"
55
"io/ioutil"
6+
"os"
67
"strings"
8+
"time"
79

8-
"github.com/deis/pkg/prettyprint"
10+
"github.com/olekukonko/tablewriter"
911

1012
"github.com/deis/workflow/client/controller/client"
1113
"github.com/deis/workflow/client/controller/models/certs"
@@ -34,45 +36,67 @@ func CertsList(results int) error {
3436
return nil
3537
}
3638

37-
certMap := make(map[string]string)
38-
nameMax := 0
39-
expiresMax := 0
39+
table := tablewriter.NewWriter(os.Stdout)
40+
table.SetAlignment(tablewriter.ALIGN_LEFT)
41+
table.SetBorder(false)
42+
table.SetAutoFormatHeaders(false)
43+
table.SetHeaderLine(true)
44+
table.SetHeader([]string{"Name", "Common Name", "SubjectAltName", "Expires", "Fingerprint", "Domains", "Updated", "Created"})
4045
for _, cert := range certList {
41-
certMap[cert.Name] = cert.Expires
42-
43-
if len(cert.Name) > nameMax {
44-
nameMax = len(cert.Name)
45-
}
46-
if len(cert.Expires) > nameMax {
47-
expiresMax = len(cert.Expires)
46+
domains := strings.Join(cert.Domains[:], ",")
47+
san := strings.Join(cert.SubjectAltName[:], ",")
48+
49+
// Make dates more readable
50+
now := time.Now()
51+
expires := cert.Expires.Time.Format("2 Jan 2006")
52+
created := cert.Created.Time.Format("2 Jan 2006")
53+
updated := cert.Updated.Time.Format("2 Jan 2006")
54+
55+
if cert.Expires.Time.Before(now) {
56+
expires += " (expired)"
57+
} else {
58+
// Ghetto solution
59+
expires += " (in"
60+
year := cert.Expires.Time.Year() - now.Year()
61+
month := cert.Expires.Time.Month() - now.Month()
62+
day := cert.Expires.Time.Day() - now.Day()
63+
64+
if year > 0 {
65+
expires += fmt.Sprintf(" %d year", year)
66+
if year > 1 {
67+
expires += "s"
68+
}
69+
} else if month > 0 {
70+
expires += fmt.Sprintf(" %d month", month)
71+
if month > 1 {
72+
expires += "s"
73+
}
74+
} else if day != 0 {
75+
// special handling on negative days
76+
if day < 0 {
77+
day *= -1
78+
}
79+
80+
expires += fmt.Sprintf(" %d day", day)
81+
if day > 1 {
82+
expires += "s"
83+
}
84+
}
85+
expires += ")"
4886
}
49-
}
5087

51-
nameHeader := "Common Name"
52-
expiresHeader := "Expires"
53-
tabSpaces := 5
54-
bufferSpaces := tabSpaces
88+
// show a shorter version of the fingerprint
89+
fingerprint := cert.Fingerprint[:5] + "[...]" + cert.Fingerprint[len(cert.Fingerprint)-5:]
5590

56-
if nameMax < len(nameHeader) {
57-
tabSpaces += len(nameHeader) - nameMax
58-
nameMax = len(nameHeader)
59-
} else {
60-
bufferSpaces += nameMax - len(nameHeader)
91+
table.Append([]string{cert.Name, cert.CommonName, san, expires, fingerprint, domains, updated, created})
6192
}
93+
table.Render()
6294

63-
if expiresMax < len(expiresHeader) {
64-
expiresMax = len(expiresHeader)
65-
}
66-
67-
fmt.Printf("%s%s%s\n", nameHeader, strings.Repeat(" ", bufferSpaces), expiresHeader)
68-
fmt.Printf("%s%s%s\n", strings.Repeat("-", nameMax), strings.Repeat(" ", 5),
69-
strings.Repeat("-", expiresMax))
70-
fmt.Print(prettyprint.PrettyTabs(certMap, tabSpaces))
7195
return nil
7296
}
7397

7498
// CertAdd adds a cert to the controller.
75-
func CertAdd(cert, key, commonName, sans string) error {
99+
func CertAdd(cert string, key string, name string) error {
76100
c, err := client.New()
77101

78102
if err != nil {
@@ -81,7 +105,7 @@ func CertAdd(cert, key, commonName, sans string) error {
81105

82106
fmt.Print("Adding SSL endpoint... ")
83107
quit := progress()
84-
err = processCertsAdd(c, cert, key, commonName, sans)
108+
err = doCertAdd(c, cert, key, name)
85109
quit <- true
86110
<-quit
87111

@@ -93,48 +117,117 @@ func CertAdd(cert, key, commonName, sans string) error {
93117
return nil
94118
}
95119

96-
func processCertsAdd(c *client.Client, cert, key, commonName, sans string) error {
97-
if sans != "" {
98-
for _, san := range strings.Split(sans, ",") {
99-
if err := doCertAdd(c, cert, key, san); err != nil {
100-
return err
101-
}
102-
}
103-
return nil
120+
func doCertAdd(c *client.Client, cert string, key string, name string) error {
121+
certFile, err := ioutil.ReadFile(cert)
122+
if err != nil {
123+
return err
124+
}
125+
126+
keyFile, err := ioutil.ReadFile(key)
127+
if err != nil {
128+
return err
104129
}
105130

106-
return doCertAdd(c, cert, key, commonName)
131+
_, err = certs.New(c, string(certFile), string(keyFile), name)
132+
return err
107133
}
108134

109-
func doCertAdd(c *client.Client, cert string, key string, commonName string) error {
110-
certFile, err := ioutil.ReadFile(cert)
135+
// CertRemove deletes a cert from the controller.
136+
func CertRemove(name string) error {
137+
c, err := client.New()
138+
if err != nil {
139+
return err
140+
}
111141

142+
fmt.Printf("Removing %s... ", name)
143+
quit := progress()
144+
145+
certs.Delete(c, name)
146+
147+
quit <- true
148+
<-quit
149+
150+
if err == nil {
151+
fmt.Println("done")
152+
}
153+
154+
return err
155+
}
156+
157+
// CertInfo gets info about certficiate
158+
func CertInfo(name string) error {
159+
c, err := client.New()
112160
if err != nil {
113161
return err
114162
}
115163

116-
keyFile, err := ioutil.ReadFile(key)
164+
cert, err := certs.Get(c, name)
165+
if err != nil {
166+
return err
167+
}
168+
169+
domains := strings.Join(cert.Domains[:], ",")
170+
if domains == "" {
171+
domains = "No connected domains"
172+
}
173+
174+
san := strings.Join(cert.SubjectAltName[:], ",")
175+
if san == "" {
176+
san = "N/A"
177+
}
178+
179+
fmt.Printf("=== %s Certificate\n", cert.Name)
180+
fmt.Println("Common Name(s): ", cert.CommonName)
181+
fmt.Println("Expires At: ", cert.Expires)
182+
fmt.Println("Starts At: ", cert.Starts)
183+
fmt.Println("Fingerprint: ", cert.Fingerprint)
184+
fmt.Println("Subject Alt Name: ", san)
185+
fmt.Println("Issuer: ", cert.Issuer)
186+
fmt.Println("Subject: ", cert.Subject)
187+
fmt.Println()
188+
fmt.Println("Connected Domains: ", domains)
189+
fmt.Println("Owner: ", cert.Owner)
190+
fmt.Println("Created: ", cert.Created)
191+
fmt.Println("Updated: ", cert.Updated)
192+
193+
return nil
194+
}
195+
196+
// CertAttach attaches a certificate to a domain
197+
func CertAttach(name string, domain string) error {
198+
c, err := client.New()
117199

118200
if err != nil {
119201
return err
120202
}
121203

122-
_, err = certs.New(c, string(certFile), string(keyFile), commonName)
204+
fmt.Printf("Attaching certificate %s to domain %s... ", name, domain)
205+
quit := progress()
206+
207+
certs.Attach(c, name, domain)
208+
209+
quit <- true
210+
<-quit
211+
212+
if err == nil {
213+
fmt.Println("done")
214+
}
215+
123216
return err
124217
}
125218

126-
// CertRemove deletes a cert from the controller.
127-
func CertRemove(commonName string) error {
219+
// CertDetach detaches a certificate from a domain
220+
func CertDetach(name string, domain string) error {
128221
c, err := client.New()
129222

130223
if err != nil {
131224
return err
132225
}
133226

134-
fmt.Printf("Removing %s... ", commonName)
227+
fmt.Printf("Detaching certificate %s from domain %s... ", name, domain)
135228
quit := progress()
136229

137-
certs.Delete(c, commonName)
230+
certs.Detach(c, name, domain)
138231

139232
quit <- true
140233
<-quit

client/controller/api/certs.go

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,34 @@
11
package api
22

3+
import "github.com/deis/pkg/time"
4+
35
// Cert is the definition of the cert object.
46
// Some fields are omtempty because they are only
57
// returned when creating or getting a cert.
68
type Cert struct {
7-
Updated string `json:"updated,omitempty"`
8-
Created string `json:"created,omitempty"`
9-
Name string `json:"common_name"`
10-
Expires string `json:"expires"`
11-
Owner string `json:"owner,omitempty"`
12-
ID int `json:"id,omitempty"`
9+
Updated time.Time `json:"updated,omitempty"`
10+
Created time.Time `json:"created,omitempty"`
11+
Name string `json:"name"`
12+
CommonName string `json:"common_name"`
13+
Expires time.Time `json:"expires"`
14+
Starts time.Time `json:"starts"`
15+
Fingerprint string `json:"fingerprint"`
16+
Issuer string `json:"issuer"`
17+
Subject string `json:"subject"`
18+
SubjectAltName []string `json:"san,omitempty"`
19+
Domains []string `json:"domains,omitempty"`
20+
Owner string `json:"owner,omitempty"`
21+
ID int `json:"id,omitempty"`
1322
}
1423

15-
// CertCreateRequest is the definition of POST /v2/certs/.
24+
// CertCreateRequest is the definition of POST and PUT to /v2/certs/
1625
type CertCreateRequest struct {
1726
Certificate string `json:"certificate"`
1827
Key string `json:"key"`
19-
Name string `json:"common_name,omitempty"`
28+
Name string `json:"name"`
29+
}
30+
31+
// CertAttachRequest is the defintion of post to /v2/certs/<cert>/domain
32+
type CertAttachRequest struct {
33+
Domain string `json:"domain"`
2034
}

client/controller/models/certs/certs.go

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,15 @@ func List(c *client.Client, results int) ([]api.Cert, int, error) {
2424
return res, count, nil
2525
}
2626

27-
// New creates a new cert.
28-
func New(c *client.Client, cert string, key string, commonName string) (api.Cert, error) {
29-
req := api.CertCreateRequest{Certificate: cert, Key: key, Name: commonName}
27+
// New creates a cert.
28+
func New(c *client.Client, cert string, key string, name string) (api.Cert, error) {
29+
req := api.CertCreateRequest{Certificate: cert, Key: key, Name: name}
3030
reqBody, err := json.Marshal(req)
31-
3231
if err != nil {
3332
return api.Cert{}, err
3433
}
3534

3635
resBody, err := c.BasicRequest("POST", "/v2/certs/", reqBody)
37-
3836
if err != nil {
3937
return api.Cert{}, err
4038
}
@@ -47,10 +45,45 @@ func New(c *client.Client, cert string, key string, commonName string) (api.Cert
4745
return resCert, nil
4846
}
4947

48+
// Get information for a certificate
49+
func Get(c *client.Client, name string) (api.Cert, error) {
50+
url := fmt.Sprintf("/v2/certs/%s", name)
51+
body, err := c.BasicRequest("GET", url, nil)
52+
if err != nil {
53+
return api.Cert{}, err
54+
}
55+
56+
res := api.Cert{}
57+
if err = json.Unmarshal([]byte(body), &res); err != nil {
58+
return api.Cert{}, err
59+
}
60+
61+
return res, nil
62+
}
63+
5064
// Delete removes a cert.
51-
func Delete(c *client.Client, commonName string) error {
52-
u := fmt.Sprintf("/v2/certs/%s", commonName)
65+
func Delete(c *client.Client, name string) error {
66+
url := fmt.Sprintf("/v2/certs/%s", name)
67+
_, err := c.BasicRequest("DELETE", url, nil)
68+
return err
69+
}
70+
71+
// Attach a certificate to a domain
72+
func Attach(c *client.Client, name string, domain string) error {
73+
req := api.CertAttachRequest{Domain: domain}
74+
reqBody, err := json.Marshal(req)
75+
if err != nil {
76+
return err
77+
}
78+
79+
url := fmt.Sprintf("/v2/certs/%s/domain/", name)
80+
_, err = c.BasicRequest("POST", url, reqBody)
81+
return err
82+
}
5383

54-
_, err := c.BasicRequest("DELETE", u, nil)
84+
// Detach a certificate from a domain
85+
func Detach(c *client.Client, name string, domain string) error {
86+
url := fmt.Sprintf("/v2/certs/%s/domain/%s", name, domain)
87+
_, err := c.BasicRequest("DELETE", url, nil)
5588
return err
5689
}

0 commit comments

Comments
 (0)