Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
22d6b868
Unverified
Commit
22d6b868
authored
Feb 09, 2017
by
Joe Beda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Introduces BootstrapSigner controller
parent
20e1f8ea
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
947 additions
and
0 deletions
+947
-0
BUILD
pkg/controller/BUILD
+1
-0
BUILD
pkg/controller/bootstrap/BUILD
+73
-0
bootstrapsigner.go
pkg/controller/bootstrap/bootstrapsigner.go
+303
-0
bootstrapsigner_test.go
pkg/controller/bootstrap/bootstrapsigner_test.go
+137
-0
common_test.go
pkg/controller/bootstrap/common_test.go
+74
-0
doc.go
pkg/controller/bootstrap/doc.go
+20
-0
jws.go
pkg/controller/bootstrap/jws.go
+64
-0
jws_test.go
pkg/controller/bootstrap/jws_test.go
+53
-0
util.go
pkg/controller/bootstrap/util.go
+85
-0
util_test.go
pkg/controller/bootstrap/util_test.go
+137
-0
No files found.
pkg/controller/BUILD
View file @
22d6b868
...
...
@@ -84,6 +84,7 @@ filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/controller/bootstrap:all-srcs",
"//pkg/controller/certificates:all-srcs",
"//pkg/controller/cloud:all-srcs",
"//pkg/controller/cronjob:all-srcs",
...
...
pkg/controller/bootstrap/BUILD
0 → 100644
View file @
22d6b868
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"bootstrapsigner_test.go",
"common_test.go",
"jws_test.go",
"util_test.go",
],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/bootstrap/api:go_default_library",
"//vendor:github.com/davecgh/go-spew/spew",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/client-go/kubernetes/fake",
"//vendor:k8s.io/client-go/pkg/api",
"//vendor:k8s.io/client-go/pkg/api/v1",
"//vendor:k8s.io/client-go/testing",
],
)
go_library(
name = "go_default_library",
srcs = [
"bootstrapsigner.go",
"doc.go",
"jws.go",
"util.go",
],
tags = ["automanaged"],
deps = [
"//pkg/bootstrap/api:go_default_library",
"//pkg/util/metrics:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:github.com/square/go-jose",
"//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/fields",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/apimachinery/pkg/watch",
"//vendor:k8s.io/client-go/kubernetes",
"//vendor:k8s.io/client-go/pkg/api",
"//vendor:k8s.io/client-go/pkg/api/v1",
"//vendor:k8s.io/client-go/tools/cache",
"//vendor:k8s.io/client-go/util/workqueue",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
pkg/controller/bootstrap/bootstrapsigner.go
0 → 100644
View file @
22d6b868
/*
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
bootstrap
import
(
"strings"
"time"
"github.com/golang/glog"
apierrors
"k8s.io/apimachinery/pkg/api/errors"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime"
utilruntime
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
clientset
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/pkg/api"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
bootstrapapi
"k8s.io/kubernetes/pkg/bootstrap/api"
"k8s.io/kubernetes/pkg/util/metrics"
)
const
(
configMapClusterInfo
=
"cluster-info"
kubeConfigKey
=
"kubeconfig"
signaturePrefix
=
"jws-kubeconfig-"
)
// BootstrapSignerOptions contains options for the BootstrapSigner
type
BootstrapSignerOptions
struct
{
// ConfigMapNamespace is the namespace of the ConfigMap
ConfigMapNamespace
string
// ConfigMapName is the name for the ConfigMap
ConfigMapName
string
// TokenSecretNamespace string is the namespace for token Secrets.
TokenSecretNamespace
string
// ConfigMapResynce is the time.Duration at which to fully re-list configmaps.
// If zero, re-list will be delayed as long as possible
ConfigMapResync
time
.
Duration
// SecretResync is the time.Duration at which to fully re-list secrets.
// If zero, re-list will be delayed as long as possible
SecretResync
time
.
Duration
}
// DefaultBootstrapSignerOptions returns a set of default options for creating a
// BootstrapSigner
func
DefaultBootstrapSignerOptions
()
BootstrapSignerOptions
{
return
BootstrapSignerOptions
{
ConfigMapNamespace
:
api
.
NamespacePublic
,
ConfigMapName
:
configMapClusterInfo
,
TokenSecretNamespace
:
api
.
NamespaceSystem
,
}
}
// BootstrapSigner is a controller that signs a ConfigMap with a set of tokens.
type
BootstrapSigner
struct
{
client
clientset
.
Interface
configMapKey
string
secretNamespace
string
configMaps
cache
.
Store
secrets
cache
.
Store
// syncQueue handles synchronizing updates to the ConfigMap. We'll only ever
// have one item (Named <ConfigMapName>) in this queue. We are using it
// serializes and collapses updates as they can come from both the ConfigMap
// and Secrets controllers.
syncQueue
workqueue
.
Interface
// Since we join two objects, we'll watch both of them with controllers.
configMapsController
cache
.
Controller
secretsController
cache
.
Controller
}
// NewBootstrapSigner returns a new *BootstrapSigner.
//
// TODO: Switch to shared informers
func
NewBootstrapSigner
(
cl
clientset
.
Interface
,
options
BootstrapSignerOptions
)
*
BootstrapSigner
{
e
:=
&
BootstrapSigner
{
client
:
cl
,
configMapKey
:
options
.
ConfigMapNamespace
+
"/"
+
options
.
ConfigMapName
,
secretNamespace
:
options
.
TokenSecretNamespace
,
syncQueue
:
workqueue
.
NewNamed
(
"bootstrap_signer_queue"
),
}
if
cl
.
Core
()
.
RESTClient
()
.
GetRateLimiter
()
!=
nil
{
metrics
.
RegisterMetricAndTrackRateLimiterUsage
(
"bootstrap_signer"
,
cl
.
Core
()
.
RESTClient
()
.
GetRateLimiter
())
}
configMapSelector
:=
fields
.
SelectorFromSet
(
map
[
string
]
string
{
api
.
ObjectNameField
:
options
.
ConfigMapName
})
e
.
configMaps
,
e
.
configMapsController
=
cache
.
NewInformer
(
&
cache
.
ListWatch
{
ListFunc
:
func
(
lo
metav1
.
ListOptions
)
(
runtime
.
Object
,
error
)
{
lo
.
FieldSelector
=
configMapSelector
.
String
()
return
e
.
client
.
Core
()
.
ConfigMaps
(
options
.
ConfigMapNamespace
)
.
List
(
lo
)
},
WatchFunc
:
func
(
lo
metav1
.
ListOptions
)
(
watch
.
Interface
,
error
)
{
lo
.
FieldSelector
=
configMapSelector
.
String
()
return
e
.
client
.
Core
()
.
ConfigMaps
(
options
.
ConfigMapNamespace
)
.
Watch
(
lo
)
},
},
&
v1
.
ConfigMap
{},
options
.
ConfigMapResync
,
cache
.
ResourceEventHandlerFuncs
{
AddFunc
:
func
(
_
interface
{})
{
e
.
pokeConfigMapSync
()
},
UpdateFunc
:
func
(
_
,
_
interface
{})
{
e
.
pokeConfigMapSync
()
},
},
)
secretSelector
:=
fields
.
SelectorFromSet
(
map
[
string
]
string
{
api
.
SecretTypeField
:
string
(
bootstrapapi
.
SecretTypeBootstrapToken
)})
e
.
secrets
,
e
.
secretsController
=
cache
.
NewInformer
(
&
cache
.
ListWatch
{
ListFunc
:
func
(
lo
metav1
.
ListOptions
)
(
runtime
.
Object
,
error
)
{
lo
.
FieldSelector
=
secretSelector
.
String
()
return
e
.
client
.
Core
()
.
Secrets
(
e
.
secretNamespace
)
.
List
(
lo
)
},
WatchFunc
:
func
(
lo
metav1
.
ListOptions
)
(
watch
.
Interface
,
error
)
{
lo
.
FieldSelector
=
secretSelector
.
String
()
return
e
.
client
.
Core
()
.
Secrets
(
e
.
secretNamespace
)
.
Watch
(
lo
)
},
},
&
v1
.
Secret
{},
options
.
SecretResync
,
cache
.
ResourceEventHandlerFuncs
{
AddFunc
:
func
(
_
interface
{})
{
e
.
pokeConfigMapSync
()
},
UpdateFunc
:
func
(
_
,
_
interface
{})
{
e
.
pokeConfigMapSync
()
},
DeleteFunc
:
func
(
_
interface
{})
{
e
.
pokeConfigMapSync
()
},
},
)
return
e
}
// Run runs controller loops and returns when they are done
func
(
e
*
BootstrapSigner
)
Run
(
stopCh
<-
chan
struct
{})
{
go
e
.
configMapsController
.
Run
(
stopCh
)
go
e
.
secretsController
.
Run
(
stopCh
)
go
wait
.
Until
(
e
.
serviceConfigMapQueue
,
0
,
stopCh
)
<-
stopCh
}
func
(
e
*
BootstrapSigner
)
pokeConfigMapSync
()
{
e
.
syncQueue
.
Add
(
e
.
configMapKey
)
}
func
(
e
*
BootstrapSigner
)
serviceConfigMapQueue
()
{
key
,
quit
:=
e
.
syncQueue
.
Get
()
if
quit
{
return
}
defer
e
.
syncQueue
.
Done
(
key
)
e
.
signConfigMap
()
}
// signConfigMap computes the signatures on our latest cached objects and writes
// back if necessary.
func
(
e
*
BootstrapSigner
)
signConfigMap
()
{
origCM
:=
e
.
getConfigMap
()
if
origCM
==
nil
{
return
}
var
needUpdate
=
false
newCM
,
err
:=
copyConfigMap
(
origCM
)
if
err
!=
nil
{
utilruntime
.
HandleError
(
err
)
return
}
// First capture the config we are signing
content
,
ok
:=
newCM
.
Data
[
kubeConfigKey
]
if
!
ok
{
glog
.
V
(
3
)
.
Infof
(
"No %s key in %s/%s ConfigMap"
,
kubeConfigKey
,
origCM
.
Namespace
,
origCM
.
Name
)
return
}
// Next remove and save all existing signatures
sigs
:=
map
[
string
]
string
{}
for
key
,
value
:=
range
newCM
.
Data
{
if
strings
.
HasPrefix
(
key
,
signaturePrefix
)
{
tokenID
:=
strings
.
TrimPrefix
(
key
,
signaturePrefix
)
sigs
[
tokenID
]
=
value
delete
(
newCM
.
Data
,
key
)
}
}
// Now recompute signatures and store them on the new map
tokens
:=
e
.
getTokens
()
for
tokenID
,
tokenValue
:=
range
tokens
{
sig
,
err
:=
computeDetachedSig
(
content
,
tokenID
,
tokenValue
)
if
err
!=
nil
{
utilruntime
.
HandleError
(
err
)
}
// Check to see if this signature is changed or new.
oldSig
,
_
:=
sigs
[
tokenID
]
if
sig
!=
oldSig
{
needUpdate
=
true
}
delete
(
sigs
,
tokenID
)
newCM
.
Data
[
signaturePrefix
+
tokenID
]
=
sig
}
// If we have signatures left over we know that some signatures were
// removed. We now need to update the ConfigMap
if
len
(
sigs
)
!=
0
{
needUpdate
=
true
}
if
needUpdate
{
e
.
updateConfigMap
(
newCM
)
}
}
func
(
e
*
BootstrapSigner
)
updateConfigMap
(
cm
*
v1
.
ConfigMap
)
{
_
,
err
:=
e
.
client
.
Core
()
.
ConfigMaps
(
cm
.
Namespace
)
.
Update
(
cm
)
if
err
!=
nil
&&
!
apierrors
.
IsConflict
(
err
)
&&
!
apierrors
.
IsNotFound
(
err
)
{
glog
.
V
(
3
)
.
Infof
(
"Error updating ConfigMap: %v"
,
err
)
}
}
// getConfigMap gets the ConfigMap we are interested in
func
(
e
*
BootstrapSigner
)
getConfigMap
()
*
v1
.
ConfigMap
{
configMap
,
exists
,
err
:=
e
.
configMaps
.
GetByKey
(
e
.
configMapKey
)
// If we can't get the configmap just return nil. The resync will eventually
// sync things up.
if
err
!=
nil
{
utilruntime
.
HandleError
(
err
)
return
nil
}
if
exists
{
return
configMap
.
(
*
v1
.
ConfigMap
)
}
return
nil
}
func
(
e
*
BootstrapSigner
)
listSecrets
()
[]
*
v1
.
Secret
{
secrets
:=
e
.
secrets
.
List
()
items
:=
[]
*
v1
.
Secret
{}
for
_
,
obj
:=
range
secrets
{
items
=
append
(
items
,
obj
.
(
*
v1
.
Secret
))
}
return
items
}
// getTokens returns a map of tokenID->tokenSecret. It ensures the token is
// valid for signing.
func
(
e
*
BootstrapSigner
)
getTokens
()
map
[
string
]
string
{
ret
:=
map
[
string
]
string
{}
secretObjs
:=
e
.
listSecrets
()
for
_
,
secret
:=
range
secretObjs
{
tokenID
,
tokenSecret
,
ok
:=
validateSecretForSigning
(
secret
)
if
!
ok
{
continue
}
// Check and warn for duplicate secrets. Behavior here will be undefined.
if
_
,
ok
:=
ret
[
tokenID
];
ok
{
glog
.
V
(
3
)
.
Infof
(
"Duplicate bootstrap tokens found for id %s, ignoring on in %s/%s"
,
tokenID
,
secret
.
Namespace
,
secret
.
Name
)
continue
}
// This secret looks good, add it to the list.
ret
[
tokenID
]
=
tokenSecret
}
return
ret
}
func
copyConfigMap
(
orig
*
v1
.
ConfigMap
)
(
*
v1
.
ConfigMap
,
error
)
{
newCMObj
,
err
:=
api
.
Scheme
.
DeepCopy
(
orig
)
if
err
!=
nil
{
return
nil
,
err
}
return
newCMObj
.
(
*
v1
.
ConfigMap
),
nil
}
pkg/controller/bootstrap/bootstrapsigner_test.go
0 → 100644
View file @
22d6b868
/*
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
bootstrap
import
(
"testing"
"github.com/davecgh/go-spew/spew"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/pkg/api"
"k8s.io/client-go/pkg/api/v1"
core
"k8s.io/client-go/testing"
)
func
init
()
{
spew
.
Config
.
DisableMethods
=
true
}
func
newBootstrapSigner
()
(
*
BootstrapSigner
,
*
fake
.
Clientset
)
{
options
:=
DefaultBootstrapSignerOptions
()
cl
:=
fake
.
NewSimpleClientset
()
return
NewBootstrapSigner
(
cl
,
options
),
cl
}
func
newConfigMap
(
tokenID
,
signature
string
)
*
v1
.
ConfigMap
{
ret
:=
&
v1
.
ConfigMap
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Namespace
:
metav1
.
NamespacePublic
,
Name
:
configMapClusterInfo
,
ResourceVersion
:
"1"
,
},
Data
:
map
[
string
]
string
{
kubeConfigKey
:
"payload"
,
},
}
if
len
(
tokenID
)
>
0
{
ret
.
Data
[
signaturePrefix
+
tokenID
]
=
signature
}
return
ret
}
func
TestNoConfigMap
(
t
*
testing
.
T
)
{
signer
,
cl
:=
newBootstrapSigner
()
signer
.
signConfigMap
()
verifyActions
(
t
,
[]
core
.
Action
{},
cl
.
Actions
())
}
func
TestSimpleSign
(
t
*
testing
.
T
)
{
signer
,
cl
:=
newBootstrapSigner
()
cm
:=
newConfigMap
(
""
,
""
)
signer
.
configMaps
.
Add
(
cm
)
secret
:=
newTokenSecret
(
"tokenID"
,
"tokenSecret"
)
addSecretSigningUsage
(
secret
,
"true"
)
signer
.
secrets
.
Add
(
secret
)
signer
.
signConfigMap
()
expected
:=
[]
core
.
Action
{
core
.
NewUpdateAction
(
schema
.
GroupVersionResource
{
Version
:
"v1"
,
Resource
:
"configmaps"
},
api
.
NamespacePublic
,
newConfigMap
(
"tokenID"
,
"eyJhbGciOiJIUzI1NiIsImtpZCI6InRva2VuSUQifQ..QAvK9DAjF0hSyASEkH1MOTB5rJMmbWEY9j-z1NSYILE"
)),
}
verifyActions
(
t
,
expected
,
cl
.
Actions
())
}
func
TestNoSignNeeded
(
t
*
testing
.
T
)
{
signer
,
cl
:=
newBootstrapSigner
()
cm
:=
newConfigMap
(
"tokenID"
,
"eyJhbGciOiJIUzI1NiIsImtpZCI6InRva2VuSUQifQ..QAvK9DAjF0hSyASEkH1MOTB5rJMmbWEY9j-z1NSYILE"
)
signer
.
configMaps
.
Add
(
cm
)
secret
:=
newTokenSecret
(
"tokenID"
,
"tokenSecret"
)
addSecretSigningUsage
(
secret
,
"true"
)
signer
.
secrets
.
Add
(
secret
)
signer
.
signConfigMap
()
verifyActions
(
t
,
[]
core
.
Action
{},
cl
.
Actions
())
}
func
TestUpdateSignature
(
t
*
testing
.
T
)
{
signer
,
cl
:=
newBootstrapSigner
()
cm
:=
newConfigMap
(
"tokenID"
,
"old signature"
)
signer
.
configMaps
.
Add
(
cm
)
secret
:=
newTokenSecret
(
"tokenID"
,
"tokenSecret"
)
addSecretSigningUsage
(
secret
,
"true"
)
signer
.
secrets
.
Add
(
secret
)
signer
.
signConfigMap
()
expected
:=
[]
core
.
Action
{
core
.
NewUpdateAction
(
schema
.
GroupVersionResource
{
Version
:
"v1"
,
Resource
:
"configmaps"
},
api
.
NamespacePublic
,
newConfigMap
(
"tokenID"
,
"eyJhbGciOiJIUzI1NiIsImtpZCI6InRva2VuSUQifQ..QAvK9DAjF0hSyASEkH1MOTB5rJMmbWEY9j-z1NSYILE"
)),
}
verifyActions
(
t
,
expected
,
cl
.
Actions
())
}
func
TestRemoveSignature
(
t
*
testing
.
T
)
{
signer
,
cl
:=
newBootstrapSigner
()
cm
:=
newConfigMap
(
"tokenID"
,
"old signature"
)
signer
.
configMaps
.
Add
(
cm
)
signer
.
signConfigMap
()
expected
:=
[]
core
.
Action
{
core
.
NewUpdateAction
(
schema
.
GroupVersionResource
{
Version
:
"v1"
,
Resource
:
"configmaps"
},
api
.
NamespacePublic
,
newConfigMap
(
""
,
""
)),
}
verifyActions
(
t
,
expected
,
cl
.
Actions
())
}
pkg/controller/bootstrap/common_test.go
0 → 100644
View file @
22d6b868
/*
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
bootstrap
import
(
"testing"
"github.com/davecgh/go-spew/spew"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/pkg/api"
"k8s.io/client-go/pkg/api/v1"
core
"k8s.io/client-go/testing"
bootstrapapi
"k8s.io/kubernetes/pkg/bootstrap/api"
)
func
newTokenSecret
(
tokenID
,
tokenSecret
string
)
*
v1
.
Secret
{
return
&
v1
.
Secret
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Namespace
:
metav1
.
NamespaceSystem
,
Name
:
"secretName"
,
ResourceVersion
:
"1"
,
},
Type
:
bootstrapapi
.
SecretTypeBootstrapToken
,
Data
:
map
[
string
][]
byte
{
bootstrapapi
.
BootstrapTokenIDKey
:
[]
byte
(
tokenID
),
bootstrapapi
.
BootstrapTokenSecretKey
:
[]
byte
(
tokenSecret
),
},
}
}
func
addSecretExpiration
(
s
*
v1
.
Secret
,
expiration
string
)
{
s
.
Data
[
bootstrapapi
.
BootstrapTokenExpirationKey
]
=
[]
byte
(
expiration
)
}
func
addSecretSigningUsage
(
s
*
v1
.
Secret
,
value
string
)
{
s
.
Data
[
bootstrapapi
.
BootstrapTokenUsageSigningKey
]
=
[]
byte
(
value
)
}
func
verifyActions
(
t
*
testing
.
T
,
expected
,
actual
[]
core
.
Action
)
{
for
i
,
a
:=
range
actual
{
if
len
(
expected
)
<
i
+
1
{
t
.
Errorf
(
"%d unexpected actions: %s"
,
len
(
actual
)
-
len
(
expected
),
spew
.
Sdump
(
actual
[
i
:
]))
break
}
e
:=
expected
[
i
]
if
!
api
.
Semantic
.
DeepEqual
(
e
,
a
)
{
t
.
Errorf
(
"Expected
\n\t
%s
\n
got
\n\t
%s"
,
spew
.
Sdump
(
e
),
spew
.
Sdump
(
a
))
continue
}
}
if
len
(
expected
)
>
len
(
actual
)
{
t
.
Errorf
(
"%d additional expected actions"
,
len
(
expected
)
-
len
(
actual
))
for
_
,
a
:=
range
expected
[
len
(
actual
)
:
]
{
t
.
Logf
(
" %s"
,
spew
.
Sdump
(
a
))
}
}
}
pkg/controller/bootstrap/doc.go
0 → 100644
View file @
22d6b868
/*
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 bootstrap provides automatic processes necessary for bootstraping.
// This includes managing and expiring tokens along with signing well known
// configmaps with those tokens.
package
bootstrap
// import "k8s.io/kubernetes/pkg/controller/bootstrap"
pkg/controller/bootstrap/jws.go
0 → 100644
View file @
22d6b868
/*
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
bootstrap
import
(
"fmt"
"strings"
jose
"github.com/square/go-jose"
)
// computeDetachedSig takes content and token details and computes a detached
// JWS signature. This is described in Appendix F of RFC 7515. Basically, this
// is a regular JWS with the content part of the signature elided.
func
computeDetachedSig
(
content
,
tokenID
,
tokenSecret
string
)
(
string
,
error
)
{
jwk
:=
&
jose
.
JsonWebKey
{
Key
:
[]
byte
(
tokenSecret
),
KeyID
:
tokenID
,
}
signer
,
err
:=
jose
.
NewSigner
(
jose
.
HS256
,
jwk
)
if
err
!=
nil
{
return
""
,
nil
}
jws
,
err
:=
signer
.
Sign
([]
byte
(
content
))
if
err
!=
nil
{
return
""
,
nil
}
fullSig
,
err
:=
jws
.
CompactSerialize
()
if
err
!=
nil
{
return
""
,
nil
}
return
stripContent
(
fullSig
)
}
// stripContent will remove the content part of a compact JWS
//
// The `go-jose` library doesn't support generating signatures with "detatched"
// content. To make up for this we take the full compact signature, break it
// apart and put it back together without the content section.
func
stripContent
(
fullSig
string
)
(
string
,
error
)
{
parts
:=
strings
.
Split
(
fullSig
,
"."
)
if
len
(
parts
)
!=
3
{
return
""
,
fmt
.
Errorf
(
"Compact JWS format must have three parts"
)
}
return
parts
[
0
]
+
".."
+
parts
[
2
],
nil
}
pkg/controller/bootstrap/jws_test.go
0 → 100644
View file @
22d6b868
/*
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
bootstrap
import
"testing"
const
(
content
=
"Hello from the other side. I must have called a thousand times."
secret
=
"my voice is my passcode"
id
=
"joshua"
)
func
TestComputeDetachedSig
(
t
*
testing
.
T
)
{
sig
,
err
:=
computeDetachedSig
(
content
,
id
,
secret
)
if
err
!=
nil
{
t
.
Errorf
(
"Error when computing signature: %v"
,
err
)
}
if
sig
!=
"eyJhbGciOiJIUzI1NiIsImtpZCI6Impvc2h1YSJ9..VShe2taLd-YTrmWuRkcL_8QTNDHYxQIEBsAYYiIj1_8"
{
t
.
Errorf
(
"Wrong signature. Got: %v"
,
sig
)
}
// Try with null content
sig
,
err
=
computeDetachedSig
(
""
,
id
,
secret
)
if
err
!=
nil
{
t
.
Errorf
(
"Error when computing signature: %v"
,
err
)
}
if
sig
!=
"eyJhbGciOiJIUzI1NiIsImtpZCI6Impvc2h1YSJ9..7Ui1ALizW4jXphVUB7xUqC9vLYLL9RZeOFfVLoB7Tgk"
{
t
.
Errorf
(
"Wrong signature. Got: %v"
,
sig
)
}
// Try with no secret
sig
,
err
=
computeDetachedSig
(
content
,
id
,
""
)
if
err
!=
nil
{
t
.
Errorf
(
"Error when computing signature: %v"
,
err
)
}
if
sig
!=
"eyJhbGciOiJIUzI1NiIsImtpZCI6Impvc2h1YSJ9..UfkqvDGiIFxrMnFseDj9LYJOLNrvjW8aHhF71mvvAs8"
{
t
.
Errorf
(
"Wrong signature. Got: %v"
,
sig
)
}
}
pkg/controller/bootstrap/util.go
0 → 100644
View file @
22d6b868
/*
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
bootstrap
import
(
"time"
"github.com/golang/glog"
"k8s.io/client-go/pkg/api/v1"
bootstrapapi
"k8s.io/kubernetes/pkg/bootstrap/api"
)
// getSecretString gets a string value from a secret. If there is an error or
// if the key doesn't exist, an empty string is returned.
func
getSecretString
(
secret
*
v1
.
Secret
,
key
string
)
string
{
data
,
ok
:=
secret
.
Data
[
key
]
if
!
ok
{
return
""
}
return
string
(
data
)
}
func
validateSecretForSigning
(
secret
*
v1
.
Secret
)
(
tokenID
,
tokenSecret
string
,
ok
bool
)
{
tokenID
=
getSecretString
(
secret
,
bootstrapapi
.
BootstrapTokenIDKey
)
if
len
(
tokenID
)
==
0
{
glog
.
V
(
3
)
.
Infof
(
"No %s key in %s/%s Secret"
,
bootstrapapi
.
BootstrapTokenIDKey
,
secret
.
Namespace
,
secret
.
Name
)
return
""
,
""
,
false
}
tokenSecret
=
getSecretString
(
secret
,
bootstrapapi
.
BootstrapTokenSecretKey
)
if
len
(
tokenSecret
)
==
0
{
glog
.
V
(
3
)
.
Infof
(
"No %s key in %s/%s Secret"
,
bootstrapapi
.
BootstrapTokenSecretKey
,
secret
.
Namespace
,
secret
.
Name
)
return
""
,
""
,
false
}
// Ensure this secret hasn't expired. The TokenCleaner should remove this
// but if that isn't working or it hasn't gotten there yet we should check
// here.
if
isSecretExpired
(
secret
)
{
return
""
,
""
,
false
}
// Make sure this secret can be used for signing
okToSign
:=
getSecretString
(
secret
,
bootstrapapi
.
BootstrapTokenUsageSigningKey
)
if
okToSign
!=
"true"
{
return
""
,
""
,
false
}
return
tokenID
,
tokenSecret
,
true
}
// isSecretExpired returns true if the Secret is expired.
func
isSecretExpired
(
secret
*
v1
.
Secret
)
bool
{
expiration
:=
getSecretString
(
secret
,
bootstrapapi
.
BootstrapTokenExpirationKey
)
if
len
(
expiration
)
>
0
{
expTime
,
err2
:=
time
.
Parse
(
time
.
RFC3339
,
expiration
)
if
err2
!=
nil
{
glog
.
V
(
3
)
.
Infof
(
"Unparseable expiration time (%s) in %s/%s Secret: %v. Treating as expired."
,
expiration
,
secret
.
Namespace
,
secret
.
Name
,
err2
)
return
true
}
if
time
.
Now
()
.
After
(
expTime
)
{
glog
.
V
(
3
)
.
Infof
(
"Expired bootstrap token in %s/%s Secret: %v"
,
secret
.
Namespace
,
secret
.
Name
,
expiration
)
return
true
}
}
return
false
}
pkg/controller/bootstrap/util_test.go
0 → 100644
View file @
22d6b868
/*
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
bootstrap
import
(
"testing"
"time"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/pkg/api/v1"
bootstrapapi
"k8s.io/kubernetes/pkg/bootstrap/api"
)
const
(
givenTokenID
=
"tokenID"
givenTokenSecret
=
"tokenSecret"
)
func
timeString
(
delta
time
.
Duration
)
string
{
return
time
.
Now
()
.
Add
(
delta
)
.
Format
(
time
.
RFC3339
)
}
func
TestValidateSecretForSigning
(
t
*
testing
.
T
)
{
cases
:=
[]
struct
{
description
string
tokenID
string
tokenSecret
string
okToSign
string
expiration
string
valid
bool
}{
{
"Signing token with no exp"
,
givenTokenID
,
givenTokenSecret
,
"true"
,
""
,
true
,
},
{
"Signing token with valid exp"
,
givenTokenID
,
givenTokenSecret
,
"true"
,
timeString
(
time
.
Hour
),
true
,
},
{
"Expired signing token"
,
givenTokenID
,
givenTokenSecret
,
"true"
,
timeString
(
-
time
.
Hour
),
false
,
},
{
"Signing token with bad exp"
,
givenTokenID
,
givenTokenSecret
,
"true"
,
"garbage"
,
false
,
},
{
"Signing token without signing bit"
,
givenTokenID
,
givenTokenSecret
,
""
,
"garbage"
,
false
,
},
{
"Signing token with bad signing bit"
,
givenTokenID
,
givenTokenSecret
,
""
,
""
,
false
,
},
{
"Signing token with no ID"
,
""
,
givenTokenSecret
,
"true"
,
""
,
false
,
},
{
"Signing token with no secret"
,
givenTokenID
,
""
,
"true"
,
""
,
false
,
},
}
for
_
,
tc
:=
range
cases
{
secret
:=
&
v1
.
Secret
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Namespace
:
metav1
.
NamespaceSystem
,
Name
:
"secretName"
,
ResourceVersion
:
"1"
,
},
Type
:
bootstrapapi
.
SecretTypeBootstrapToken
,
Data
:
map
[
string
][]
byte
{
bootstrapapi
.
BootstrapTokenIDKey
:
[]
byte
(
tc
.
tokenID
),
bootstrapapi
.
BootstrapTokenSecretKey
:
[]
byte
(
tc
.
tokenSecret
),
bootstrapapi
.
BootstrapTokenUsageSigningKey
:
[]
byte
(
tc
.
okToSign
),
bootstrapapi
.
BootstrapTokenExpirationKey
:
[]
byte
(
tc
.
expiration
),
},
}
tokenID
,
tokenSecret
,
ok
:=
validateSecretForSigning
(
secret
)
if
ok
!=
tc
.
valid
{
t
.
Errorf
(
"%s: Unexpected validation failure. Expected %v, got %v"
,
tc
.
description
,
tc
.
valid
,
ok
)
}
if
ok
{
if
tokenID
!=
tc
.
tokenID
{
t
.
Errorf
(
"%s: Unexpected Token ID. Expected %q, got %q"
,
tc
.
description
,
givenTokenID
,
tokenID
)
}
if
tokenSecret
!=
tc
.
tokenSecret
{
t
.
Errorf
(
"%s: Unexpected Token Secret. Expected %q, got %q"
,
tc
.
description
,
givenTokenSecret
,
tokenSecret
)
}
}
}
}
func
TestValidateSecret
(
t
*
testing
.
T
)
{
secret
:=
&
v1
.
Secret
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Namespace
:
metav1
.
NamespaceSystem
,
Name
:
"secretName"
,
ResourceVersion
:
"1"
,
},
Type
:
bootstrapapi
.
SecretTypeBootstrapToken
,
Data
:
map
[
string
][]
byte
{
bootstrapapi
.
BootstrapTokenIDKey
:
[]
byte
(
givenTokenID
),
bootstrapapi
.
BootstrapTokenSecretKey
:
[]
byte
(
givenTokenSecret
),
bootstrapapi
.
BootstrapTokenUsageSigningKey
:
[]
byte
(
"true"
),
},
}
tokenID
,
tokenSecret
,
ok
:=
validateSecretForSigning
(
secret
)
if
!
ok
{
t
.
Errorf
(
"Unexpected validation failure."
)
}
if
tokenID
!=
givenTokenID
{
t
.
Errorf
(
"Unexpected Token ID. Expected %q, got %q"
,
givenTokenID
,
tokenID
)
}
if
tokenSecret
!=
givenTokenSecret
{
t
.
Errorf
(
"Unexpected Token Secret. Expected %q, got %q"
,
givenTokenSecret
,
tokenSecret
)
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment