Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport "Optionally provide ca_pin as a file path" (#12796) to v9 #13089

Merged
merged 4 commits into from
Jun 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/auth/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func Register(params RegisterParams) (*proto.Certs, error) {
params.setDefaults()
// Read in the token. The token can either be passed in or come from a file
// on disk.
token, err := utils.ReadToken(params.Token)
token, err := utils.TryReadValueAsFile(params.Token)
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down
8 changes: 4 additions & 4 deletions lib/config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,8 @@ func ApplyFileConfig(fc *FileConfig, cfg *service.Config) error {

// Read in how nodes will validate the CA. A single empty string in the file
// conf should indicate no pins.
if len(fc.CAPin) > 1 || (len(fc.CAPin) == 1 && fc.CAPin[0] != "") {
cfg.CAPins = fc.CAPin
if err = cfg.ApplyCAPins(fc.CAPin); err != nil {
return trace.Wrap(err)
}

// Set diagnostic address
Expand Down Expand Up @@ -1833,8 +1833,8 @@ func Configure(clf *CommandLineFlags, cfg *service.Config) error {
}

// Apply flags used for the node to validate the Auth Server.
if len(clf.CAPins) != 0 {
cfg.CAPins = clf.CAPins
if err = cfg.ApplyCAPins(clf.CAPins); err != nil {
return trace.Wrap(err)
}

// apply --listen-ip flag:
Expand Down
35 changes: 31 additions & 4 deletions lib/config/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -626,11 +626,28 @@ teleport:

func TestApplyConfig(t *testing.T) {
tempDir := t.TempDir()
tokenPath := filepath.Join(tempDir, "small-config-token")
err := os.WriteFile(tokenPath, []byte("join-token"), 0644)
authTokenPath := filepath.Join(tempDir, "small-config-token")
err := os.WriteFile(authTokenPath, []byte("join-token"), 0o644)
require.NoError(t, err)

conf, err := ReadConfig(bytes.NewBufferString(fmt.Sprintf(SmallConfigString, tokenPath)))
caPinPath := filepath.Join(tempDir, "small-config-ca-pin")
err = os.WriteFile(caPinPath, []byte("ca-pin-from-file1\nca-pin-from-file2"), 0o644)
require.NoError(t, err)

staticTokenPath := filepath.Join(tempDir, "small-config-static-tokens")
err = os.WriteFile(staticTokenPath, []byte("token-from-file1\ntoken-from-file2"), 0o644)
require.NoError(t, err)

pkcs11LibPath := filepath.Join(tempDir, "fake-pkcs11-lib.so")
err = os.WriteFile(pkcs11LibPath, []byte("fake-pkcs11-lib"), 0o644)
require.NoError(t, err)

conf, err := ReadConfig(bytes.NewBufferString(fmt.Sprintf(
SmallConfigString,
authTokenPath,
caPinPath,
staticTokenPath,
)))
require.NoError(t, err)
require.NotNil(t, conf)
require.Equal(t, apiutils.Strings{"web3:443"}, conf.Proxy.PublicAddr)
Expand All @@ -646,6 +663,16 @@ func TestApplyConfig(t *testing.T) {
Roles: types.SystemRoles([]types.SystemRole{"Proxy", "Node"}),
Expires: time.Unix(0, 0).UTC(),
},
{
Token: "token-from-file1",
Roles: types.SystemRoles([]types.SystemRole{"Node"}),
Expires: time.Unix(0, 0).UTC(),
},
{
Token: "token-from-file2",
Roles: types.SystemRoles([]types.SystemRole{"Node"}),
Expires: time.Unix(0, 0).UTC(),
},
{
Token: "yyy",
Roles: types.SystemRoles([]types.SystemRole{"Auth"}),
Expand Down Expand Up @@ -720,7 +747,7 @@ SREzU8onbBsjMg9QDiSf5oJLKvd/Ren+zGY7
require.Equal(t, "example_token", cfg.Auth.KeyStore.TokenLabel)
require.Equal(t, 1, *cfg.Auth.KeyStore.SlotNumber)
require.Equal(t, "example_pin", cfg.Auth.KeyStore.Pin)
require.Empty(t, cfg.CAPins)
require.ElementsMatch(t, []string{"ca-pin-from-string", "ca-pin-from-file1", "ca-pin-from-file2"}, cfg.CAPins)
}

// TestApplyConfigNoneEnabled makes sure that if a section is not enabled,
Expand Down
39 changes: 24 additions & 15 deletions lib/config/fileconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -754,28 +754,32 @@ func (c ClusterName) Parse() (types.ClusterName, error) {
type StaticTokens []StaticToken

func (t StaticTokens) Parse() (types.StaticTokens, error) {
staticTokens := []types.ProvisionTokenV1{}
var provisionTokens []types.ProvisionTokenV1

for _, token := range t {
st, err := token.Parse()
for _, st := range t {
tokens, err := st.Parse()
if err != nil {
return nil, trace.Wrap(err)
}
staticTokens = append(staticTokens, *st)
provisionTokens = append(provisionTokens, tokens...)
}

return types.NewStaticTokens(types.StaticTokensSpecV2{
StaticTokens: staticTokens,
StaticTokens: provisionTokens,
})
}

type StaticToken string

// Parse is applied to a string in "role,role,role:token" format. It breaks it
// apart and constructs a services.ProvisionToken which contains the token,
// apart and constructs a list of services.ProvisionToken which contains the token,
// role, and expiry (infinite).
func (t StaticToken) Parse() (*types.ProvisionTokenV1, error) {
parts := strings.Split(string(t), ":")
// If the token string is a file path, the file may contain multiple newline delimited
// tokens, in which case each token is used to construct a services.ProvisionToken
// with the same roles.
func (t StaticToken) Parse() ([]types.ProvisionTokenV1, error) {
// Split only on the first ':', for future cross platform compat with windows paths
parts := strings.SplitN(string(t), ":", 2)
if len(parts) != 2 {
return nil, trace.BadParameter("invalid static token spec: %q", t)
}
Expand All @@ -785,16 +789,21 @@ func (t StaticToken) Parse() (*types.ProvisionTokenV1, error) {
return nil, trace.Wrap(err)
}

token, err := utils.ReadToken(parts[1])
tokenPart, err := utils.TryReadValueAsFile(parts[1])
if err != nil {
return nil, trace.Wrap(err)
}

return &types.ProvisionTokenV1{
Token: token,
Roles: roles,
Expires: time.Unix(0, 0).UTC(),
}, nil
tokens := strings.Split(tokenPart, "\n")
provisionTokens := make([]types.ProvisionTokenV1, 0, len(tokens))

for _, token := range tokens {
provisionTokens = append(provisionTokens, types.ProvisionTokenV1{
Token: token,
Roles: roles,
Expires: time.Unix(0, 0).UTC(),
})
}
return provisionTokens, nil
}

// AuthenticationConfig describes the auth_service/authentication section of teleport.yaml
Expand Down
31 changes: 31 additions & 0 deletions lib/config/fileconf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,37 @@ func TestAuthenticationSection(t *testing.T) {
}
}

func TestAuthenticationConfig_Parse_StaticToken(t *testing.T) {
t.Parallel()

tests := []struct {
desc string
token string
}{
{desc: "file path on windows", token: `C:\path\to\some\file`},
{desc: "literal string", token: "some-literal-token"},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
staticToken := StaticToken("Auth,Node,Proxy:" + tt.token)
provisionTokens, err := staticToken.Parse()
require.NoError(t, err)

require.Len(t, provisionTokens, 1)
provisionToken := provisionTokens[0]

want := types.ProvisionTokenV1{
Roles: []types.SystemRole{
types.RoleAuth, types.RoleNode, types.RoleProxy,
},
Token: tt.token,
Expires: provisionToken.Expires,
}
require.Equal(t, provisionToken, want)
})
}
}

func TestAuthenticationConfig_Parse_nilU2F(t *testing.T) {
// An absent U2F section should be reflected as a nil U2F object.
// The config below is a valid config without U2F, but other than that we
Expand Down
5 changes: 4 additions & 1 deletion lib/config/testdata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,16 @@ teleport:
average: 170
burst: 171
diag_addr: 127.0.0.1:3000
ca_pin: ""
ca_pin:
- ca-pin-from-string
- %v
auth_service:
enabled: yes
listen_addr: 10.5.5.1:3025
cluster_name: magadan
tokens:
- "proxy,node:xxx"
- "node:%v"
- "auth:yyy"
ca_key_params:
pkcs11:
Expand Down
24 changes: 23 additions & 1 deletion lib/service/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ type Config struct {
func (cfg *Config) ApplyToken(token string) (bool, error) {
if token != "" {
var err error
cfg.Token, err = utils.ReadToken(token)
cfg.Token, err = utils.TryReadValueAsFile(token)
if err != nil {
return false, trace.Wrap(err)
}
Expand All @@ -279,6 +279,28 @@ func (cfg *Config) ApplyToken(token string) (bool, error) {
return false, nil
}

// ApplyCAPins assigns the given CA pin(s), filtering out empty pins.
// If a pin is specified as a path to a file, that file must not be empty.
func (cfg *Config) ApplyCAPins(caPins []string) error {
var filteredPins []string
for _, pinOrPath := range caPins {
if pinOrPath == "" {
continue
}
pins, err := utils.TryReadValueAsFile(pinOrPath)
if err != nil {
return trace.Wrap(err)
}
// an empty pin file is less obvious than a blank ca_pin in the config yaml.
if pins == "" {
return trace.BadParameter("empty ca_pin file: %v", pinOrPath)
}
filteredPins = append(filteredPins, strings.Split(pins, "\n")...)
}
cfg.CAPins = filteredPins
return nil
}

// RoleConfig is a config for particular Teleport role
func (cfg *Config) RoleConfig() RoleConfig {
return RoleConfig{
Expand Down
24 changes: 16 additions & 8 deletions lib/utils/token.go → lib/utils/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,31 @@ package utils

import (
"io/ioutil"
"path/filepath"
"strings"

"github.com/gravitational/trace"
log "github.com/sirupsen/logrus"
)

// ReadToken is a utility function to read the token
// from the disk if it looks like a path,
// otherwise, treat it as a value
func ReadToken(token string) (string, error) {
if !strings.HasPrefix(token, "/") {
return token, nil
// TryReadValueAsFile is a utility function to read a value
// from the disk if it looks like an absolute path,
// otherwise, treat it as a value.
// It only support absolute paths to avoid ambiguity in interpretation of the value
func TryReadValueAsFile(value string) (string, error) {
if !filepath.IsAbs(value) {
return value, nil
}
// treat it as a file
out, err := ioutil.ReadFile(token)
contents, err := ioutil.ReadFile(value)
if err != nil {
return "", trace.ConvertSystemError(err)
}
// trim newlines as tokens in files tend to have newlines
return strings.TrimSpace(string(out)), nil
out := strings.TrimSpace(string(contents))

if out == "" {
log.Warnf("Empty config value file: %v", value)
}
return out, nil
}
8 changes: 4 additions & 4 deletions lib/utils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -503,21 +503,21 @@ func TestMarshalYAML(t *testing.T) {
}

// TestReadToken tests reading token from file and as is
func TestReadToken(t *testing.T) {
func TestTryReadValueAsFile(t *testing.T) {
t.Parallel()
tok, err := ReadToken("token")
tok, err := TryReadValueAsFile("token")
require.Equal(t, "token", tok)
require.NoError(t, err)

_, err = ReadToken("/tmp/non-existent-token-for-teleport-tests-not-found")
_, err = TryReadValueAsFile("/tmp/non-existent-token-for-teleport-tests-not-found")
fixtures.AssertNotFound(t, err)

dir := t.TempDir()
tokenPath := filepath.Join(dir, "token")
err = os.WriteFile(tokenPath, []byte("shmoken"), 0644)
require.NoError(t, err)

tok, err = ReadToken(tokenPath)
tok, err = TryReadValueAsFile(tokenPath)
require.NoError(t, err)
require.Equal(t, "shmoken", tok)
}
Expand Down