mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-12 21:36:24 +00:00
Expand on kube-discovery API and integrate container build.
This commit is contained in:
parent
e3278d965a
commit
baebd7cfd9
18
cluster/images/kube-discovery/Dockerfile
Normal file
18
cluster/images/kube-discovery/Dockerfile
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Copyright 2016 The Kubernetes Authors.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
FROM BASEIMAGE
|
||||||
|
|
||||||
|
COPY kube-discovery /usr/local/bin
|
||||||
|
ENTRYPOINT "/usr/local/bin/kube-discovery"
|
53
cluster/images/kube-discovery/Makefile
Normal file
53
cluster/images/kube-discovery/Makefile
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Copyright 2016 The Kubernetes Authors.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# Build the kube-discovery image.
|
||||||
|
#
|
||||||
|
# Requires a pre-built kube-discovery binary:
|
||||||
|
# build/run.sh /bin/bash -c "KUBE_BUILD_PLATFORMS=linux/ARCH make WHAT=cmd/kube-discovery"
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# [ARCH=amd64] [REGISTRY="gcr.io/google_containers"] make (build|push) VERSION={some_released_version_of_kubernetes}
|
||||||
|
|
||||||
|
REGISTRY?=gcr.io/google_containers
|
||||||
|
ARCH?=amd64
|
||||||
|
TEMP_DIR:=$(shell mktemp -d)
|
||||||
|
VERSION?=1.0
|
||||||
|
|
||||||
|
ifeq ($(ARCH),amd64)
|
||||||
|
BASEIMAGE?=debian:jessie
|
||||||
|
endif
|
||||||
|
ifeq ($(ARCH),arm)
|
||||||
|
BASEIMAGE?=armel/debian:jessie
|
||||||
|
endif
|
||||||
|
ifeq ($(ARCH),arm64)
|
||||||
|
BASEIMAGE?=aarch64/debian:jessie
|
||||||
|
endif
|
||||||
|
ifeq ($(ARCH),ppc64le)
|
||||||
|
BASEIMAGE?=ppc64le/debian:jessie
|
||||||
|
endif
|
||||||
|
|
||||||
|
all: build
|
||||||
|
|
||||||
|
build:
|
||||||
|
cp -r ./* ${TEMP_DIR}
|
||||||
|
cp ../../../_output/dockerized/bin/linux/${ARCH}/kube-discovery ${TEMP_DIR}
|
||||||
|
cd ${TEMP_DIR} && sed -i.back "s|BASEIMAGE|${BASEIMAGE}|g" Dockerfile
|
||||||
|
docker build -t ${REGISTRY}/kube-discovery-${ARCH}:${VERSION} ${TEMP_DIR}
|
||||||
|
rm -rf "${TEMP_DIR}"
|
||||||
|
|
||||||
|
push: build
|
||||||
|
gcloud docker push ${REGISTRY}/kube-discovery-${ARCH}:${VERSION}
|
||||||
|
|
||||||
|
.PHONY: all
|
45
cluster/images/kube-discovery/README.md
Normal file
45
cluster/images/kube-discovery/README.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
### kube-discovery
|
||||||
|
|
||||||
|
An initial implementation of a Kubernetes discovery service using JSON Web Signatures.
|
||||||
|
|
||||||
|
This prototype is configured by kubeadm and run within Kubernetes itself.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
This pod expects the cluster CA, endpoints list, and token map to exist in /tmp/secret. This allows us to pass them in as kubernetes secrets when deployed as a pod.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cd /tmp/secret
|
||||||
|
$ ls
|
||||||
|
ca.pem endpoint-list.json token-map.json
|
||||||
|
$ cat endpoint-list.json
|
||||||
|
["http://192.168.1.5:8080", "http://192.168.1.6:8080"]
|
||||||
|
$ cat token-map.json
|
||||||
|
{
|
||||||
|
"TOKENID": "ABCDEF1234123456"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build And Run From Source
|
||||||
|
|
||||||
|
```
|
||||||
|
$ build/run.sh /bin/bash -c "KUBE_BUILD_PLATFORMS=linux/amd64 make WHAT=cmd/kube-discovery"
|
||||||
|
$ _output/dockerized/bin/linux/amd64/kube-discovery
|
||||||
|
2016/08/23 19:17:28 Listening for requests on port 9898.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running in Docker
|
||||||
|
|
||||||
|
This image is published at: gcr.io/google_containers/kube-discovery
|
||||||
|
|
||||||
|
`docker run -d -p 9898:9898 -v /tmp/secret/ca.pem:/tmp/secret/ca.pem -v /tmp/secret/endpoint-list.json:/tmp/secret/endpoint-list.json -v /tmp/secret/token-map.json:/tmp/secret/token-map.json --name kubediscovery gcr.io/google_containers/kube-discovery`
|
||||||
|
|
||||||
|
## Testing the API
|
||||||
|
|
||||||
|
`curl "http://localhost:9898/cluster-info/v1/?token-id=TOKENID"`
|
||||||
|
|
||||||
|
You should see JSON containing a signed payload. For code to verify and decode that payload see handler_test.go.
|
||||||
|
|
||||||
|
|
||||||
|
[]()
|
@ -1,19 +1,22 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2016 The Kubernetes Authors.
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
package kubediscovery
|
|
||||||
|
package discovery
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -45,13 +48,13 @@ type fsCALoader struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cl *fsCALoader) LoadPEM() (string, error) {
|
func (cl *fsCALoader) LoadPEM() (string, error) {
|
||||||
if cl.certData != "" {
|
if cl.certData == "" {
|
||||||
data, err := ioutil.ReadFile(CAPath)
|
data, err := ioutil.ReadFile(CAPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
cl.certData = base64.StdEncoding.EncodeToString(data)
|
cl.certData = string(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cl.certData, nil
|
return cl.certData, nil
|
||||||
@ -92,11 +95,11 @@ type endpointsLoader interface {
|
|||||||
LoadList() ([]string, error)
|
LoadList() ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type jsonFileEnpointsLoader struct {
|
type jsonFileEndpointsLoader struct {
|
||||||
endpoints []string
|
endpoints []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (el *jsonFileEnpointsLoader) LoadList() ([]string, error) {
|
func (el *jsonFileEndpointsLoader) LoadList() ([]string, error) {
|
||||||
if len(el.endpoints) == 0 {
|
if len(el.endpoints) == 0 {
|
||||||
data, err := ioutil.ReadFile(EndpointListPath)
|
data, err := ioutil.ReadFile(EndpointListPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -121,7 +124,7 @@ func NewClusterInfoHandler() *ClusterInfoHandler {
|
|||||||
return &ClusterInfoHandler{
|
return &ClusterInfoHandler{
|
||||||
tokenLoader: &jsonFileTokenLoader{},
|
tokenLoader: &jsonFileTokenLoader{},
|
||||||
caLoader: &fsCALoader{},
|
caLoader: &fsCALoader{},
|
||||||
endpointsLoader: &jsonFileEnpointsLoader{},
|
endpointsLoader: &jsonFileEndpointsLoader{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +133,7 @@ func (cih *ClusterInfoHandler) ServeHTTP(resp http.ResponseWriter, req *http.Req
|
|||||||
log.Printf("Got token ID: %s", tokenID)
|
log.Printf("Got token ID: %s", tokenID)
|
||||||
token, err := cih.tokenLoader.LoadAndLookup(tokenID)
|
token, err := cih.tokenLoader.LoadAndLookup(tokenID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Invalid token: %s", err)
|
log.Print(err)
|
||||||
http.Error(resp, "Forbidden", http.StatusForbidden)
|
http.Error(resp, "Forbidden", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -138,6 +141,7 @@ func (cih *ClusterInfoHandler) ServeHTTP(resp http.ResponseWriter, req *http.Req
|
|||||||
|
|
||||||
// TODO probably should not leak server-side errors to the client
|
// TODO probably should not leak server-side errors to the client
|
||||||
caPEM, err := cih.caLoader.LoadPEM()
|
caPEM, err := cih.caLoader.LoadPEM()
|
||||||
|
log.Printf("Loaded CA: %s", caPEM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Error loading root CA certificate data: %s", err)
|
err = fmt.Errorf("Error loading root CA certificate data: %s", err)
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@ -159,7 +163,10 @@ func (cih *ClusterInfoHandler) ServeHTTP(resp http.ResponseWriter, req *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Instantiate an signer using HMAC-SHA256.
|
// Instantiate an signer using HMAC-SHA256.
|
||||||
signer, err := jose.NewSigner(jose.HS256, []byte(token))
|
hmacKey := []byte(token)
|
||||||
|
|
||||||
|
log.Printf("Key is %d bytes long", len(hmacKey))
|
||||||
|
signer, err := jose.NewSigner(jose.HS256, hmacKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Error creating JWS signer: %s", err)
|
err = fmt.Errorf("Error creating JWS signer: %s", err)
|
||||||
log.Println(err)
|
log.Println(err)
|
208
cmd/kube-discovery/app/handlers_test.go
Normal file
208
cmd/kube-discovery/app/handlers_test.go
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
|
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 discovery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/square/go-jose"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockTokenLoader struct {
|
||||||
|
tokenID string
|
||||||
|
token string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tl *mockTokenLoader) LoadAndLookup(tokenID string) (string, error) {
|
||||||
|
if tokenID == tl.tokenID {
|
||||||
|
return tl.token, nil
|
||||||
|
}
|
||||||
|
return "", errors.New(fmt.Sprintf("invalid token: %s", tokenID))
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockEndpoint1 = "https://192.168.1.5:8080"
|
||||||
|
const mockEndpoint2 = "https://192.168.1.6:8080"
|
||||||
|
|
||||||
|
type mockEndpointsLoader struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (el *mockEndpointsLoader) LoadList() ([]string, error) {
|
||||||
|
return []string{mockEndpoint1, mockEndpoint2}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockCA = "---BEGIN------END---DUMMYDATA"
|
||||||
|
|
||||||
|
type mockCALoader struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *mockCALoader) LoadPEM() (string, error) {
|
||||||
|
return mockCA, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockTokenID = "AAAAAA"
|
||||||
|
const mockToken = "9537434E638E4378"
|
||||||
|
|
||||||
|
const mockTokenIDCustom = "SHAREDSECRET"
|
||||||
|
const mockTokenCustom = "VERYSECRETTOKEN"
|
||||||
|
|
||||||
|
func TestClusterInfoIndex(t *testing.T) {
|
||||||
|
longToken := strings.Repeat("a", 1000)
|
||||||
|
tests := map[string]struct {
|
||||||
|
tokenID string // token ID the mock loader will use
|
||||||
|
token string // token the mock loader will use
|
||||||
|
reqTokenID string // token ID the will request with
|
||||||
|
reqToken string // token the caller will validate response with
|
||||||
|
expStatus int
|
||||||
|
expVerifyFailure bool
|
||||||
|
}{
|
||||||
|
"no token": {
|
||||||
|
tokenID: mockTokenID,
|
||||||
|
token: mockToken,
|
||||||
|
reqTokenID: "",
|
||||||
|
reqToken: "",
|
||||||
|
expStatus: http.StatusForbidden,
|
||||||
|
},
|
||||||
|
"valid token ID": {
|
||||||
|
tokenID: mockTokenID,
|
||||||
|
token: mockToken,
|
||||||
|
reqTokenID: mockTokenID,
|
||||||
|
reqToken: mockToken,
|
||||||
|
expStatus: http.StatusOK,
|
||||||
|
},
|
||||||
|
"valid arbitrary string token": {
|
||||||
|
tokenID: mockTokenIDCustom,
|
||||||
|
token: mockTokenCustom,
|
||||||
|
reqTokenID: mockTokenIDCustom,
|
||||||
|
reqToken: mockTokenCustom,
|
||||||
|
expStatus: http.StatusOK,
|
||||||
|
},
|
||||||
|
"valid arbitrary long string token": {
|
||||||
|
tokenID: "LONGTOKENTEST",
|
||||||
|
token: longToken,
|
||||||
|
reqTokenID: "LONGTOKENTEST",
|
||||||
|
reqToken: longToken,
|
||||||
|
expStatus: http.StatusOK,
|
||||||
|
},
|
||||||
|
"invalid token ID": {
|
||||||
|
tokenID: mockTokenID,
|
||||||
|
token: mockToken,
|
||||||
|
reqTokenID: "BADTOKENID",
|
||||||
|
reqToken: mockToken,
|
||||||
|
expStatus: http.StatusForbidden,
|
||||||
|
},
|
||||||
|
"invalid token": {
|
||||||
|
tokenID: mockTokenID,
|
||||||
|
token: mockToken,
|
||||||
|
reqTokenID: mockTokenID,
|
||||||
|
reqToken: "badtoken",
|
||||||
|
expStatus: http.StatusOK,
|
||||||
|
expVerifyFailure: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, test := range tests {
|
||||||
|
t.Logf("Running test: %s", name)
|
||||||
|
tokenLoader := &mockTokenLoader{test.tokenID, test.token}
|
||||||
|
// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
|
||||||
|
// pass 'nil' as the third parameter.
|
||||||
|
url := "/cluster-info/v1/"
|
||||||
|
if test.tokenID != "" {
|
||||||
|
url = fmt.Sprintf("%s?token-id=%s", url, test.reqTokenID)
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
handler := &ClusterInfoHandler{
|
||||||
|
tokenLoader: tokenLoader,
|
||||||
|
caLoader: &mockCALoader{},
|
||||||
|
endpointsLoader: &mockEndpointsLoader{},
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
if status := rr.Code; status != test.expStatus {
|
||||||
|
t.Errorf("handler returned wrong status code: got %v want %v",
|
||||||
|
status, test.expStatus)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we were expecting valid status validate the body:
|
||||||
|
if test.expStatus == http.StatusOK {
|
||||||
|
var ci ClusterInfo
|
||||||
|
|
||||||
|
body := string(rr.Body.Bytes())
|
||||||
|
|
||||||
|
// Parse the JSON web signature:
|
||||||
|
jws, err := jose.ParseSigned(body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error parsing JWS from request body: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we can verify the signature on the payload. An error here would
|
||||||
|
// indicate the the message failed to verify, e.g. because the signature was
|
||||||
|
// broken or the message was tampered with.
|
||||||
|
var clusterInfoBytes []byte
|
||||||
|
hmacTestKey := []byte(test.reqToken)
|
||||||
|
clusterInfoBytes, err = jws.Verify(hmacTestKey)
|
||||||
|
|
||||||
|
if test.expVerifyFailure {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Signature verification did not fail as expected.")
|
||||||
|
}
|
||||||
|
// We are done the test here either way.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error verifing signature: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(clusterInfoBytes, &ci)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unable to unmarshall payload to JSON: error=%s body=%s", err, rr.Body.String())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(ci.Endpoints) != 2 {
|
||||||
|
t.Errorf("Expected 2 endpoints, got: %d", len(ci.Endpoints))
|
||||||
|
}
|
||||||
|
if mockEndpoint1 != ci.Endpoints[0] {
|
||||||
|
t.Errorf("Unexpected endpoint: %s", ci.Endpoints[0])
|
||||||
|
}
|
||||||
|
if mockEndpoint2 != ci.Endpoints[1] {
|
||||||
|
t.Errorf("Unexpected endpoint: %s", ci.Endpoints[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ci.CertificateAuthorities) != 1 {
|
||||||
|
t.Errorf("Expected 1 root certificate, got: %d", len(ci.CertificateAuthorities))
|
||||||
|
}
|
||||||
|
if ci.CertificateAuthorities[0] != mockCA {
|
||||||
|
t.Errorf("Expected CA: %s, got: %s", mockCA, ci.CertificateAuthorities[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2016 The Kubernetes Authors.
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
package kubediscovery
|
|
||||||
|
package discovery
|
||||||
|
|
||||||
type ClusterInfo struct {
|
type ClusterInfo struct {
|
||||||
// TODO Kind, apiVersion
|
// TODO Kind, apiVersion
|
@ -1,16 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2016 The Kubernetes Authors.
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
package kubediscovery
|
|
||||||
|
package discovery
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2014 The Kubernetes Authors.
|
|
||||||
|
|
||||||
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 main
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "github.com/square/go-jose"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
}
|
|
@ -1,15 +1,19 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2016 The Kubernetes Authors.
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -17,7 +21,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
kd "k8s.io/kubernetes/pkg/kubediscovery"
|
kd "k8s.io/kubernetes/cmd/kube-discovery/app"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
@ -1,6 +0,0 @@
|
|||||||
FROM golang
|
|
||||||
|
|
||||||
ADD kubediscovery /usr/bin/
|
|
||||||
ENTRYPOINT /usr/bin/kubernetes-discovery
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
@ -1,39 +0,0 @@
|
|||||||
# kubernetes-discovery
|
|
||||||
An initial implementation of a Kubernetes discovery service using JSON Web Signatures.
|
|
||||||
|
|
||||||
This prototype is expected to be run by Kubernetes itself for the time being,
|
|
||||||
and will hopefully be merged into the core API at a later time.
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
Generate a CA cert save it to: /tmp/secret/ca.pem to run the service or unit tests. (will not be required for unit tests for long) Similarly when run within kubernetes we expect a secret to be provided at this location as well. (see below)
|
|
||||||
|
|
||||||
## Build And Run From Source
|
|
||||||
|
|
||||||
```
|
|
||||||
$ make WHAT=cmd/kubediscovery
|
|
||||||
$ _output/local/bin/linux/amd64/kubediscovery
|
|
||||||
2016/08/23 19:17:28 Listening for requests on port 9898.
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running in Docker
|
|
||||||
|
|
||||||
This image is published temporarily on Docker Hub as dgoodwin/kubediscovery
|
|
||||||
|
|
||||||
`docker run --rm -p 9898:9898 -v /tmp/secret/ca.pem:/tmp/secret/ca.pem --name kubediscovery dgoodwin/kubediscovery`
|
|
||||||
|
|
||||||
## Running in Kubernetes
|
|
||||||
|
|
||||||
A dummy certificate is included in ca-secret.yaml.
|
|
||||||
|
|
||||||
```
|
|
||||||
kubectl create -f ca-secret.yaml
|
|
||||||
kubectl create -f kubediscovery.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing the API
|
|
||||||
|
|
||||||
`curl "http://localhost:9898/cluster-info/v1/?token-id=TOKENID"`
|
|
||||||
|
|
||||||
You should see JSON containing a signed payload. For code to verify and decode that payload see handler_test.go.
|
|
@ -1,7 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: ca-secret
|
|
||||||
type: Opaque
|
|
||||||
data:
|
|
||||||
ca.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR4RENDQXF5Z0F3SUJBZ0lVV3pqUDl5RUk0eHlRSnBzVHVERU4yV2ROaUFzd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2FERUxNQWtHQTFVRUJoTUNWVk14RHpBTkJnTlZCQWdUQms5eVpXZHZiakVSTUE4R0ExVUVCeE1JVUc5eQpkR3hoYm1ReEV6QVJCZ05WQkFvVENrdDFZbVZ5Ym1WMFpYTXhDekFKQmdOVkJBc1RBa05CTVJNd0VRWURWUVFECkV3cExkV0psY201bGRHVnpNQjRYRFRFMk1EZ3hNVEUyTkRnd01Gb1hEVEl4TURneE1ERTJORGd3TUZvd2FERUwKTUFrR0ExVUVCaE1DVlZNeER6QU5CZ05WQkFnVEJrOXlaV2R2YmpFUk1BOEdBMVVFQnhNSVVHOXlkR3hoYm1ReApFekFSQmdOVkJBb1RDa3QxWW1WeWJtVjBaWE14Q3pBSkJnTlZCQXNUQWtOQk1STXdFUVlEVlFRREV3cExkV0psCmNtNWxkR1Z6TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF3QkhNOGN6anc0Q1cKK05wbklhV012RzZlcVhtelNZT20vbHdaNUhOMnVLck9xaTNHYUUyTjFKd2tzcGRmMXNOUGFZMHdPR2xkbURIZgoxSnlyTW8rUFdLVUVjWko1WGE4Vm02d2I0MlpjczN3MEp5dlEzWFJjaDQyMFJRWGRKayszcmMybWRvSVRkL0lmCnZjWms0N0RzQTMrQU5QSUlSTzdWRmZpS1JNRFpTUDR1OThnVjI2eW1zbjc0TzFVKzNVUHR1TEFTVTFLck9FTk4KR01FWG0ydTJpdmVvbTJrbjFlZTZuM1hCR1o2bU52cUNPdWUxRXdza0gvWkhoUVh1UDgyV1U5dVk0aGVORnoyQwpBNmR0Q0Q0c3Z6eHc3ZFQ2cVhsV0ZIWUYrc3VLVDhXNkczd3NkOWxzV0ZVY0ZWL0lwaTVobEVaTWprNFNoY3RqCjdpYnlrRURKM1FJREFRQUJvMll3WkRBT0JnTlZIUThCQWY4RUJBTUNBUVl3RWdZRFZSMFRBUUgvQkFnd0JnRUIKL3dJQkFqQWRCZ05WSFE0RUZnUVVOdnhRZ3o5ZTNXS2VscU1KTmZXNE1KUHYzc0V3SHdZRFZSMGpCQmd3Rm9BVQpOdnhRZ3o5ZTNXS2VscU1KTmZXNE1KUHYzc0V3RFFZSktvWklodmNOQVFFTEJRQURnZ0VCQUp1TUhYUms1TEVyCmxET1p4Mm9aRUNQZ29reXMzSGJsM05oempXd2pncXdxNVN6a011V3QrUnVkdnRTK0FUQjFtTjRjYTN0eSt2bWcKT09heTkvaDZoditmSE5jZHpYdWR5dFZYZW1KN3F4ZFoxd25DUUcwdnpqOWRZY0xFSGpJWi94dU1jNlY3dnJ4YwpSc0preGp5aE01UXBmRHd0eVZKeGpkUmVBZ0huSyswTkNieHdtQ3cyRGIvOXpudm9LWGk4TEQwbkQzOFQxY3R3CmhmdGxwTmRoZXFNRlpEZXBuTUYwY2g2cHo5TFV5Mkh1cnhrV2dkWVNjY2VNU0hPTzBMcG4xeVVBMWczOTJhUjUKWk81Zm5KMW95Vm1LVWFCeDJCMndsSVlUSXlES1ZiMnY1UXNHbnYvRHVTMDZhcmVLTmsvTGpHRTRlMXlHOHJkcwpacnZHMzNvUmtEbz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
|
|
@ -1,37 +0,0 @@
|
|||||||
apiVersion: extensions/v1beta1
|
|
||||||
kind: ReplicaSet
|
|
||||||
metadata:
|
|
||||||
name: kubediscovery
|
|
||||||
# these labels can be applied automatically
|
|
||||||
# from the labels in the pod template if not set
|
|
||||||
# labels:
|
|
||||||
# app: guestbook
|
|
||||||
# tier: frontend
|
|
||||||
spec:
|
|
||||||
# this replicas value is default
|
|
||||||
# modify it according to your case
|
|
||||||
replicas: 1
|
|
||||||
# selector can be applied automatically
|
|
||||||
# from the labels in the pod template if not set,
|
|
||||||
# but we are specifying the selector here to
|
|
||||||
# demonstrate its usage.
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: kubediscovery
|
|
||||||
spec:
|
|
||||||
hostNetwork: true
|
|
||||||
containers:
|
|
||||||
- name: kubediscovery
|
|
||||||
image: dgoodwin/kubediscovery
|
|
||||||
imagePullPolicy: IfNotPresent
|
|
||||||
ports:
|
|
||||||
- containerPort: 9898
|
|
||||||
volumeMounts:
|
|
||||||
- name: ca-secret-vol
|
|
||||||
mountPath: /tmp/secret
|
|
||||||
readOnly: true
|
|
||||||
volumes:
|
|
||||||
- name: ca-secret-vol
|
|
||||||
secret:
|
|
||||||
secretName: ca-secret
|
|
@ -1,100 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes Authors.
|
|
||||||
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 kubediscovery
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/square/go-jose"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestClusterInfoIndex(t *testing.T) {
|
|
||||||
tests := map[string]struct {
|
|
||||||
url string
|
|
||||||
expStatus int
|
|
||||||
}{
|
|
||||||
"no token": {
|
|
||||||
"/cluster-info/v1/",
|
|
||||||
http.StatusForbidden,
|
|
||||||
},
|
|
||||||
"valid token": {
|
|
||||||
fmt.Sprintf("/cluster-info/v1/?token-id=%s", tempTokenId),
|
|
||||||
http.StatusOK,
|
|
||||||
},
|
|
||||||
"invalid token": {
|
|
||||||
"/cluster-info/v1/?token-id=JUNK",
|
|
||||||
http.StatusForbidden,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, test := range tests {
|
|
||||||
t.Logf("Running test: %s", name)
|
|
||||||
// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
|
|
||||||
// pass 'nil' as the third parameter.
|
|
||||||
req, err := http.NewRequest("GET", test.url, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
|
||||||
// TODO: mock/stub here
|
|
||||||
handler := NewClusterInfoHandler()
|
|
||||||
|
|
||||||
handler.ServeHTTP(rr, req)
|
|
||||||
|
|
||||||
if status := rr.Code; status != test.expStatus {
|
|
||||||
t.Errorf("handler returned wrong status code: got %v want %v",
|
|
||||||
status, test.expStatus)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we were expecting valid status validate the body:
|
|
||||||
if test.expStatus == http.StatusOK {
|
|
||||||
var ci ClusterInfo
|
|
||||||
|
|
||||||
body := string(rr.Body.Bytes())
|
|
||||||
|
|
||||||
// Parse the JSON web signature:
|
|
||||||
jws, err := jose.ParseSigned(body)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error parsing JWS from request body: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we can verify the signature on the payload. An error here would
|
|
||||||
// indicate the the message failed to verify, e.g. because the signature was
|
|
||||||
// broken or the message was tampered with.
|
|
||||||
var clusterInfoBytes []byte
|
|
||||||
hmacTestKey := fromHexBytes(tempToken)
|
|
||||||
clusterInfoBytes, err = jws.Verify(hmacTestKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error verifing signature: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(clusterInfoBytes, &ci)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unable to unmarshall payload to JSON: error=%s body=%s", err, rr.Body.String())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ci.RootCertificates == "" {
|
|
||||||
t.Error("No root certificates in response")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user