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
e3431ffb
Commit
e3431ffb
authored
Sep 23, 2020
by
Brad Davidson
Committed by
Brad Davidson
Oct 28, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Simplify token parsing
Improves readability, reduces round-trips to the join server to validate certs. Signed-off-by:
Brad Davidson
<
brad.davidson@rancher.com
>
parent
3d8118b4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
104 additions
and
96 deletions
+104
-96
config.go
pkg/agent/config/config.go
+2
-3
run.go
pkg/agent/run.go
+2
-2
clientaccess.go
pkg/clientaccess/clientaccess.go
+97
-85
bootstrap.go
pkg/cluster/bootstrap.go
+3
-6
No files found.
pkg/agent/config/config.go
View file @
e3431ffb
...
...
@@ -54,13 +54,12 @@ func Get(ctx context.Context, agent cmds.Agent, proxy proxy.Proxy) *config.Node
type
HTTPRequester
func
(
u
string
,
client
*
http
.
Client
,
username
,
password
string
)
([]
byte
,
error
)
func
Request
(
path
string
,
info
*
clientaccess
.
Info
,
requester
HTTPRequester
)
([]
byte
,
error
)
{
u
,
err
:=
url
.
Parse
(
info
.
URL
)
u
,
err
:=
url
.
Parse
(
info
.
Base
URL
)
if
err
!=
nil
{
return
nil
,
err
}
u
.
Path
=
path
username
,
password
,
_
:=
clientaccess
.
ParseUsernamePassword
(
info
.
Token
)
return
requester
(
u
.
String
(),
clientaccess
.
GetHTTPClient
(
info
.
CACerts
),
username
,
password
)
return
requester
(
u
.
String
(),
clientaccess
.
GetHTTPClient
(
info
.
CACerts
),
info
.
Username
,
info
.
Password
)
}
func
getNodeNamedCrt
(
nodeName
,
nodeIP
,
nodePasswordFile
string
)
HTTPRequester
{
...
...
pkg/agent/run.go
View file @
e3431ffb
...
...
@@ -152,7 +152,7 @@ func Run(ctx context.Context, cfg cmds.Agent) error {
}
for
{
newToken
,
err
:=
clientaccess
.
Normaliz
eAndValidateTokenForUser
(
proxy
.
SupervisorURL
(),
cfg
.
Token
,
"node"
)
newToken
,
err
:=
clientaccess
.
Pars
eAndValidateTokenForUser
(
proxy
.
SupervisorURL
(),
cfg
.
Token
,
"node"
)
if
err
!=
nil
{
logrus
.
Error
(
err
)
select
{
...
...
@@ -162,7 +162,7 @@ func Run(ctx context.Context, cfg cmds.Agent) error {
}
continue
}
cfg
.
Token
=
newToken
cfg
.
Token
=
newToken
.
String
()
break
}
...
...
pkg/clientaccess/clientaccess.go
View file @
e3431ffb
...
...
@@ -26,15 +26,15 @@ var (
}
)
type
OverrideURLCallback
func
(
config
[]
byte
)
(
*
url
.
URL
,
error
)
const
(
tokenPrefix
=
"K10"
tokenFormat
=
"%s%s::%s:%s"
)
type
clientToken
struct
{
caHash
string
username
string
password
string
}
type
OverrideURLCallback
func
(
config
[]
byte
)
(
*
url
.
URL
,
error
)
func
WriteClientKubeConfig
(
destFile
,
url
,
serverCAFile
,
clientCertFile
,
clientKeyFile
string
)
error
{
// WriteClientKubeConfig generates a kubeconfig at destFile that can be used to connect to a server at url with the given certs and keys
func
WriteClientKubeConfig
(
destFile
string
,
url
string
,
serverCAFile
string
,
clientCertFile
string
,
clientKeyFile
string
)
error
{
serverCA
,
err
:=
ioutil
.
ReadFile
(
serverCAFile
)
if
err
!=
nil
{
return
errors
.
Wrapf
(
err
,
"failed to read %s"
,
serverCAFile
)
...
...
@@ -73,96 +73,71 @@ func WriteClientKubeConfig(destFile, url, serverCAFile, clientCertFile, clientKe
}
type
Info
struct
{
URL
string
`json:"url,omitempty"`
CACerts
[]
byte
`json:"cacerts,omitempty"`
username
string
password
string
Token
string
`json:"token,omitempty"`
}
func
(
i
*
Info
)
ToToken
()
string
{
return
fmt
.
Sprintf
(
"K10%s::%s:%s"
,
hashCA
(
i
.
CACerts
),
i
.
username
,
i
.
password
)
BaseURL
string
`json:"baseurl,omitempty"`
Username
string
`json:"username,omitempty"`
Password
string
`json:"password,omitempty"`
caHash
string
}
func
NormalizeAndValidateTokenForUser
(
server
,
token
,
user
string
)
(
string
,
error
)
{
if
!
strings
.
HasPrefix
(
token
,
"K10"
)
{
token
=
"K10::"
+
user
+
":"
+
token
}
info
,
err
:=
ParseAndValidateToken
(
server
,
token
)
if
err
!=
nil
{
return
""
,
err
}
if
info
.
username
!=
user
{
info
.
username
=
user
}
return
info
.
ToToken
(),
nil
// String returns the token data, templated according to the token format
func
(
info
*
Info
)
String
()
string
{
return
fmt
.
Sprintf
(
tokenFormat
,
tokenPrefix
,
hashCA
(
info
.
CACerts
),
info
.
Username
,
info
.
Password
)
}
func
ParseAndValidateToken
(
server
,
token
string
)
(
*
Info
,
error
)
{
url
,
err
:=
url
.
Parse
(
server
)
// ParseAndValidateToken parses a token, downloads and validates the server's CA bundle,
// and validates it according to the caHash from the token if set.
func
ParseAndValidateToken
(
server
string
,
token
string
)
(
*
Info
,
error
)
{
info
,
err
:=
parseToken
(
token
)
if
err
!=
nil
{
return
nil
,
err
ors
.
Wrapf
(
err
,
"Invalid url, failed to parse %s"
,
server
)
return
nil
,
err
}
if
url
.
Scheme
!=
"https"
{
return
nil
,
fmt
.
Errorf
(
"only https:// URLs are supported, invalid scheme: %s"
,
server
)
if
err
:=
info
.
setServer
(
server
);
err
!=
nil
{
return
nil
,
err
}
for
strings
.
HasSuffix
(
url
.
Path
,
"/"
)
{
url
.
Path
=
url
.
Path
[
:
len
(
url
.
Path
)
-
1
]
if
info
.
caHash
!=
""
{
if
err
:=
info
.
validateCAHash
();
err
!=
nil
{
return
nil
,
err
}
}
parsedToken
,
err
:=
parseToken
(
token
)
if
err
!=
nil
{
return
nil
,
err
}
return
info
,
nil
}
cacerts
,
err
:=
GetCACerts
(
*
url
)
// ParseAndValidateToken parses a token with user override, downloads and
// validates the server's CA bundle, and validates it according to the caHash from the token if set.
func
ParseAndValidateTokenForUser
(
server
string
,
token
string
,
username
string
)
(
*
Info
,
error
)
{
info
,
err
:=
parseToken
(
token
)
if
err
!=
nil
{
return
nil
,
err
}
if
len
(
cacerts
)
>
0
&&
len
(
parsedToken
.
caHash
)
>
0
{
if
ok
,
hash
,
newHash
:=
validateCACerts
(
cacerts
,
parsedToken
.
caHash
);
!
ok
{
return
nil
,
fmt
.
Errorf
(
"token does not match the server %s != %s"
,
hash
,
newHash
)
}
}
info
.
Username
=
username
if
err
:=
validateToken
(
*
url
,
cacerts
,
parsedToken
.
username
,
parsedToken
.
password
);
err
!=
nil
{
if
err
:=
info
.
setServer
(
server
);
err
!=
nil
{
return
nil
,
err
}
i
:=
&
Info
{
URL
:
url
.
String
(),
CACerts
:
cacerts
,
username
:
parsedToken
.
username
,
password
:
parsedToken
.
password
,
Token
:
token
,
if
info
.
caHash
!=
""
{
if
err
:=
info
.
validateCAHash
();
err
!=
nil
{
return
nil
,
err
}
}
// normalize token
i
.
Token
=
i
.
ToToken
()
return
i
,
nil
return
info
,
nil
}
func
validateToken
(
u
url
.
URL
,
cacerts
[]
byte
,
username
,
password
string
)
error
{
u
.
Path
=
"/cacerts"
_
,
err
:=
get
(
u
.
String
(),
GetHTTPClient
(
cacerts
),
username
,
password
)
if
err
!=
nil
{
return
errors
.
Wrap
(
err
,
"token is not valid"
)
}
return
nil
}
func
validateCACerts
(
cacerts
[]
byte
,
hash
string
)
(
bool
,
string
,
string
)
{
// validateCACerts returns a boolean indicating whether or not a CA bundle matches the provided hash,
// and a string containing the hash of the CA bundle.
func
validateCACerts
(
cacerts
[]
byte
,
hash
string
)
(
bool
,
string
)
{
if
len
(
cacerts
)
==
0
&&
hash
==
""
{
return
true
,
""
,
""
return
true
,
""
}
newHash
:=
hashCA
(
cacerts
)
return
hash
==
newHash
,
hash
,
newHash
return
hash
==
newHash
,
newHash
}
// hashCA returns the hex-encoded SHA256 digest of a byte array.
...
...
@@ -174,38 +149,40 @@ func hashCA(cacerts []byte) string {
// ParseUsernamePassword returns the username and password portion of a token string,
// along with a bool indicating if the token was successfully parsed.
func
ParseUsernamePassword
(
token
string
)
(
string
,
string
,
bool
)
{
parsed
,
err
:=
parseToken
(
token
)
info
,
err
:=
parseToken
(
token
)
if
err
!=
nil
{
return
""
,
""
,
false
}
return
parsed
.
username
,
parsed
.
p
assword
,
true
return
info
.
Username
,
info
.
P
assword
,
true
}
func
parseToken
(
token
string
)
(
clientToken
,
error
)
{
var
result
clientToken
// parseToken parses a token into an Info struct
func
parseToken
(
token
string
)
(
*
Info
,
error
)
{
var
info
=
&
Info
{}
if
!
strings
.
HasPrefix
(
token
,
"K10"
)
{
return
result
,
fmt
.
Errorf
(
"token is not a valid token format"
)
if
!
strings
.
HasPrefix
(
token
,
tokenPrefix
)
{
token
=
fmt
.
Sprintf
(
tokenFormat
,
tokenPrefix
,
""
,
""
,
token
)
}
token
=
token
[
3
:
]
// Strip off the prefix
token
=
token
[
len
(
tokenPrefix
)
:
]
parts
:=
strings
.
SplitN
(
token
,
"::"
,
2
)
token
=
parts
[
0
]
if
len
(
parts
)
>
1
{
result
.
caHash
=
parts
[
0
]
info
.
caHash
=
parts
[
0
]
token
=
parts
[
1
]
}
parts
=
strings
.
SplitN
(
token
,
":"
,
2
)
if
len
(
parts
)
!=
2
{
return
result
,
fmt
.
Errorf
(
"token credentials are the wrong
format"
)
return
nil
,
fmt
.
Errorf
(
"invalid token
format"
)
}
result
.
u
sername
=
parts
[
0
]
result
.
p
assword
=
parts
[
1
]
info
.
U
sername
=
parts
[
0
]
info
.
P
assword
=
parts
[
1
]
return
result
,
nil
return
info
,
nil
}
// GetHTTPClient returns a http client that validates TLS server certificates using the provided CA bundle.
...
...
@@ -232,18 +209,53 @@ func GetHTTPClient(cacerts []byte) *http.Client {
// Get makes a request to a subpath of info's BaseURL
func
Get
(
path
string
,
info
*
Info
)
([]
byte
,
error
)
{
u
,
err
:=
url
.
Parse
(
info
.
URL
)
u
,
err
:=
url
.
Parse
(
info
.
Base
URL
)
if
err
!=
nil
{
return
nil
,
err
}
u
.
Path
=
path
return
get
(
u
.
String
(),
GetHTTPClient
(
info
.
CACerts
),
info
.
username
,
info
.
password
)
return
get
(
u
.
String
(),
GetHTTPClient
(
info
.
CACerts
),
info
.
Username
,
info
.
Password
)
}
// setServer sets the BaseURL and CACerts fields of the Info by connecting to the server
// and storing the CA bundle.
func
(
info
*
Info
)
setServer
(
server
string
)
error
{
url
,
err
:=
url
.
Parse
(
server
)
if
err
!=
nil
{
return
errors
.
Wrapf
(
err
,
"Invalid server url, failed to parse: %s"
,
server
)
}
if
url
.
Scheme
!=
"https"
{
return
fmt
.
Errorf
(
"only https:// URLs are supported, invalid scheme: %s"
,
server
)
}
for
strings
.
HasSuffix
(
url
.
Path
,
"/"
)
{
url
.
Path
=
url
.
Path
[
:
len
(
url
.
Path
)
-
1
]
}
cacerts
,
err
:=
getCACerts
(
*
url
)
if
err
!=
nil
{
return
err
}
info
.
BaseURL
=
url
.
String
()
info
.
CACerts
=
cacerts
return
nil
}
// ValidateCAHash validates that info's caHash matches the CACerts hash.
func
(
info
*
Info
)
validateCAHash
()
error
{
if
ok
,
serverHash
:=
validateCACerts
(
info
.
CACerts
,
info
.
caHash
);
!
ok
{
return
fmt
.
Errorf
(
"token CA hash does not match the server CA hash: %s != %s"
,
info
.
caHash
,
serverHash
)
}
return
nil
}
// getCACerts retrieves the CA bundle from a server.
// An error is raised if the CA bundle cannot be retrieved,
// or if the server's cert is not signed by the returned bundle.
func
G
etCACerts
(
u
url
.
URL
)
([]
byte
,
error
)
{
func
g
etCACerts
(
u
url
.
URL
)
([]
byte
,
error
)
{
u
.
Path
=
"/cacerts"
url
:=
u
.
String
()
...
...
@@ -258,7 +270,7 @@ func GetCACerts(u url.URL) ([]byte, error) {
// Download the CA bundle using a client that does not validate certs.
cacerts
,
err
:=
get
(
url
,
insecureClient
,
""
,
""
)
if
err
!=
nil
{
return
nil
,
errors
.
Wrap
f
(
err
,
"failed to get CA certs at %s"
,
url
)
return
nil
,
errors
.
Wrap
(
err
,
"failed to get CA certs"
)
}
// Request the CA bundle again, validating that the CA bundle can be loaded
...
...
@@ -266,7 +278,7 @@ func GetCACerts(u url.URL) ([]byte, error) {
// get an empty CA bundle. or if the dynamiclistener cert is incorrectly signed.
_
,
err
=
get
(
url
,
GetHTTPClient
(
cacerts
),
""
,
""
)
if
err
!=
nil
{
return
nil
,
errors
.
Wrap
f
(
err
,
"server %s is not trusted"
,
url
)
return
nil
,
errors
.
Wrap
(
err
,
"CA cert validation failed"
)
}
return
cacerts
,
nil
...
...
pkg/cluster/bootstrap.go
View file @
e3431ffb
...
...
@@ -64,12 +64,9 @@ func (c *Cluster) shouldBootstrapLoad(ctx context.Context) (bool, error) {
return
false
,
errors
.
New
(
version
.
ProgramUpper
+
"_TOKEN is required to join a cluster"
)
}
token
,
err
:=
clientaccess
.
NormalizeAndValidateTokenForUser
(
c
.
config
.
JoinURL
,
c
.
config
.
Token
,
"server"
)
if
err
!=
nil
{
return
false
,
err
}
info
,
err
:=
clientaccess
.
ParseAndValidateToken
(
c
.
config
.
JoinURL
,
token
)
// Fail if the token isn't syntactically valid, or if the CA hash on the remote server doesn't match
// the hash in the token. The password isn't actually checked until later when actually bootstrapping.
info
,
err
:=
clientaccess
.
ParseAndValidateTokenForUser
(
c
.
config
.
JoinURL
,
c
.
config
.
Token
,
"server"
)
if
err
!=
nil
{
return
false
,
err
}
...
...
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