forked from concourse/concourse
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
atc: behaviour: add pipeline-identity var_source
Pipelines should be able to fetch a signed token containing information about their idenity concourse/rfcs/pull/139 Signed-off-by: Daniel Baumgarten <[email protected]>
- Loading branch information
1 parent
67b05c3
commit b000a00
Showing
9 changed files
with
323 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package idtoken | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"time" | ||
|
||
"code.cloudfoundry.org/lager/v3" | ||
|
||
"github.com/concourse/concourse/atc/creds" | ||
) | ||
|
||
type Manager struct { | ||
Config map[string]interface{} | ||
|
||
TokenGenerator TokenGenerator | ||
} | ||
|
||
func (manager *Manager) Init(log lager.Logger) error { | ||
aud := make([]string, 0, 1) | ||
if audList, ok := manager.Config["aud"].([]interface{}); ok { | ||
for _, e := range audList { | ||
if audience, ok := e.(string); ok { | ||
aud = append(aud, audience) | ||
} | ||
} | ||
} | ||
if len(aud) == 0 { | ||
aud = append(aud, defaultAudience...) | ||
} | ||
|
||
ttl := defaultTTL | ||
var err error | ||
ttlString, ok := manager.Config["ttl"].(string) | ||
if ok { | ||
ttl, err = time.ParseDuration(ttlString) | ||
if err != nil { | ||
return fmt.Errorf("invalid idtoken provider config: invalid TTL value: %w", err) | ||
} | ||
} | ||
|
||
signKey, err := GenerateNewKey() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
manager.TokenGenerator = TokenGenerator{ | ||
Issuer: "https://test", | ||
Audiences: aud, | ||
TTL: ttl, | ||
Key: signKey, | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (manager *Manager) MarshalJSON() ([]byte, error) { | ||
health, err := manager.Health() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return json.Marshal(&map[string]interface{}{ | ||
"health": health, | ||
}) | ||
} | ||
|
||
func (manager Manager) IsConfigured() bool { | ||
return false | ||
} | ||
|
||
func (manager Manager) Validate() error { | ||
return nil | ||
} | ||
|
||
func (manager Manager) Health() (*creds.HealthResponse, error) { | ||
return &creds.HealthResponse{ | ||
Method: "noop", | ||
}, nil | ||
} | ||
|
||
func (manager Manager) Close(logger lager.Logger) { | ||
|
||
} | ||
|
||
func (manager Manager) NewSecretsFactory(logger lager.Logger) (creds.SecretsFactory, error) { | ||
return NewSecretsFactory(&manager.TokenGenerator), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package idtoken | ||
|
||
import ( | ||
"fmt" | ||
"time" | ||
|
||
"github.com/concourse/concourse/atc/creds" | ||
flags "github.com/jessevdk/go-flags" | ||
) | ||
|
||
type managerFactory struct{} | ||
|
||
var defaultAudience = []string{"concourse-pipeline-idp"} | ||
var defaultTTL = 15 * time.Minute | ||
|
||
func init() { | ||
creds.Register("idtoken", NewManagerFactory()) | ||
} | ||
|
||
func NewManagerFactory() creds.ManagerFactory { | ||
return &managerFactory{} | ||
} | ||
|
||
func (factory *managerFactory) AddConfig(group *flags.Group) creds.Manager { | ||
return &Manager{} | ||
} | ||
|
||
func (factory *managerFactory) NewInstance(config interface{}) (creds.Manager, error) { | ||
configMap, ok := config.(map[string]interface{}) | ||
if !ok { | ||
return nil, fmt.Errorf("invalid idtoken provider config: %T", config) | ||
} | ||
|
||
return &Manager{ | ||
Config: configMap, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package idtoken | ||
|
||
import ( | ||
"fmt" | ||
"path" | ||
"strings" | ||
"time" | ||
|
||
"github.com/concourse/concourse/atc/creds" | ||
) | ||
|
||
type Secrets struct { | ||
TokenGenerator *TokenGenerator | ||
} | ||
|
||
type fixedSecretPath struct { | ||
fixedPath string | ||
} | ||
|
||
func (p fixedSecretPath) VariableToSecretPath(_ string) (string, error) { | ||
return p.fixedPath, nil | ||
} | ||
|
||
func (secrets *Secrets) NewSecretLookupPaths(teamName string, pipelineName string, allowRootPath bool) []creds.SecretLookupPath { | ||
// there is no real "loolupPath" involved | ||
// just return something from which team and pipeline can be extracted later | ||
return []creds.SecretLookupPath{fixedSecretPath{path.Join(teamName, pipelineName)}} | ||
} | ||
|
||
func (secrets *Secrets) Get(secretPath string) (interface{}, *time.Time, bool, error) { | ||
parts := strings.Split(secretPath, "/") | ||
if len(parts) != 2 { | ||
return nil, nil, false, fmt.Errorf("secretPath should have exactly 2 parts") | ||
} | ||
team := parts[0] | ||
pipeline := parts[1] | ||
|
||
token, _, err := secrets.TokenGenerator.GenerateToken(team, pipeline) | ||
if err != nil { | ||
return nil, nil, false, err | ||
} | ||
|
||
return token, nil, true, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package idtoken | ||
|
||
import ( | ||
"github.com/concourse/concourse/atc/creds" | ||
) | ||
|
||
type SecretsFactory struct { | ||
TokenGenerator *TokenGenerator | ||
} | ||
|
||
func NewSecretsFactory(TokenGenerator *TokenGenerator) *SecretsFactory { | ||
return &SecretsFactory{ | ||
TokenGenerator: TokenGenerator, | ||
} | ||
} | ||
|
||
func (factory *SecretsFactory) NewSecrets() creds.Secrets { | ||
return &Secrets{ | ||
TokenGenerator: factory.TokenGenerator, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package idtoken | ||
|
||
import ( | ||
"crypto/rand" | ||
"crypto/rsa" | ||
"fmt" | ||
"math" | ||
"math/big" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"github.com/lestrrat-go/jwx/v3/jwa" | ||
"github.com/lestrrat-go/jwx/v3/jwk" | ||
"github.com/lestrrat-go/jwx/v3/jwt" | ||
) | ||
|
||
type TokenGenerator struct { | ||
Issuer string | ||
Key jwk.Key | ||
Audiences []string | ||
TTL time.Duration | ||
} | ||
|
||
func (g TokenGenerator) GenerateToken(team, pipeline string) (token string, validUntil time.Time, err error) { | ||
now := time.Now() | ||
validUntil = now.Add(g.TTL) | ||
|
||
unsigned, err := jwt.NewBuilder(). | ||
Issuer(g.Issuer). | ||
IssuedAt(now). | ||
NotBefore(now). | ||
Audience(g.Audiences). | ||
Subject(generateSubject(team, pipeline)). | ||
Expiration(validUntil). | ||
JwtID(generateJTI()). | ||
Claim("team", team). | ||
Claim("pipeline", pipeline). | ||
Build() | ||
|
||
if err != nil { | ||
return "", time.Time{}, err | ||
} | ||
|
||
signed, err := jwt.Sign(unsigned, jwt.WithKey(jwa.RS256(), g.Key)) | ||
if err != nil { | ||
return "", time.Time{}, err | ||
} | ||
|
||
return string(signed), validUntil, nil | ||
} | ||
|
||
func (g TokenGenerator) IsTokenStillValid(token string) (bool, time.Time, error) { | ||
parsed, err := jwt.Parse([]byte(token), jwt.WithKey(jwa.RS256(), g.Key)) | ||
if err != nil { | ||
if strings.Contains(err.Error(), "token is expired") { | ||
return false, time.Time{}, nil | ||
} | ||
return false, time.Time{}, err | ||
} | ||
|
||
exp, exists := parsed.Expiration() | ||
if !exists { | ||
return false, time.Time{}, err | ||
} | ||
|
||
return true, exp, nil | ||
} | ||
|
||
func generateSubject(team, pipeline string) string { | ||
return fmt.Sprintf("%s/%s", team, pipeline) | ||
} | ||
|
||
func generateJTI() string { | ||
num, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) | ||
if err != nil { | ||
// should never happen | ||
panic(err) | ||
} | ||
return strconv.Itoa(int(num.Int64())) | ||
} | ||
|
||
func GenerateNewKey() (jwk.Key, error) { | ||
privateKey, err := rsa.GenerateKey(rand.Reader, 4096) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
key, err := jwk.Import(privateKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
key.Set("kid", generateKID()) | ||
key.Set("iat", time.Now().Unix()) | ||
|
||
return key, nil | ||
} | ||
|
||
func generateKID() string { | ||
num, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) | ||
if err != nil { | ||
// should never happen | ||
panic(err) | ||
} | ||
return strconv.Itoa(int(num.Int64())) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters