Skip to content

Commit

Permalink
atc: behaviour: add pipeline-identity var_source
Browse files Browse the repository at this point in the history
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
dbaumgarten committed Dec 3, 2024
1 parent 67b05c3 commit b000a00
Show file tree
Hide file tree
Showing 9 changed files with 323 additions and 1 deletion.
1 change: 1 addition & 0 deletions atc/atccmd/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ import (
_ "github.com/concourse/concourse/atc/creds/conjur"
_ "github.com/concourse/concourse/atc/creds/credhub"
_ "github.com/concourse/concourse/atc/creds/dummy"
_ "github.com/concourse/concourse/atc/creds/idtoken"
_ "github.com/concourse/concourse/atc/creds/kubernetes"
_ "github.com/concourse/concourse/atc/creds/secretsmanager"
_ "github.com/concourse/concourse/atc/creds/ssm"
Expand Down
2 changes: 1 addition & 1 deletion atc/configvalidate/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ func validateVarSources(c atc.Config) ([]atc.ConfigWarning, error) {
// TODO: this check should eventually be removed once all credential managers
// are supported in pipeline. - @evanchaoli
switch varSource.Type {
case "vault", "dummy", "ssm", "secretsmanager":
case "vault", "dummy", "ssm", "secretsmanager", "idtoken":
default:
errorMessages = append(errorMessages, fmt.Sprintf("credential manager type %s is not supported in pipeline yet", varSource.Type))
}
Expand Down
88 changes: 88 additions & 0 deletions atc/creds/idtoken/manager.go
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
}
37 changes: 37 additions & 0 deletions atc/creds/idtoken/manager_factory.go
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
}
44 changes: 44 additions & 0 deletions atc/creds/idtoken/secrets.go
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
}
21 changes: 21 additions & 0 deletions atc/creds/idtoken/secrets_factory.go
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,
}
}
107 changes: 107 additions & 0 deletions atc/creds/idtoken/token_generator.go
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()))
}
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ require (
github.com/containerd/platforms v0.2.1 // indirect
github.com/coreos/go-oidc/v3 v3.11.0 // indirect
github.com/danieljoos/wincred v1.2.2 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
Expand All @@ -215,6 +216,7 @@ require (
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
Expand All @@ -225,6 +227,11 @@ require (
github.com/huandu/xstrings v1.5.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc/v3 v3.0.0-beta1 // indirect
github.com/lestrrat-go/jwx/v3 v3.0.0-alpha1 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
Expand All @@ -235,6 +242,7 @@ require (
github.com/nxadm/tail v1.4.11 // indirect
github.com/openzipkin/zipkin-go v0.4.3 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
Expand Down
16 changes: 16 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/dexidp/dex/api/v2 v2.2.0 h1:lMehnAw5NwMorWcCf5ncwzqe0uJ/g/PtK7DR4K8uM5E=
github.com/dexidp/dex/api/v2 v2.2.0/go.mod h1:wW7pJO4K9SxAgwjTWGv9jzBS6sbKcItdtbC25uJoZvg=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
Expand Down Expand Up @@ -879,6 +881,8 @@ github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3a
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM=
github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
Expand Down Expand Up @@ -1127,6 +1131,16 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhR
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/httprc/v3 v3.0.0-beta1 h1:pzDjP9dSONCFQC/AE3mWUnHILGiYPiMKzQIS+weKJXA=
github.com/lestrrat-go/httprc/v3 v3.0.0-beta1/go.mod h1:wdsgouffPvWPEYh8t7PRH/PidR5sfVqt0na4Nhj60Ms=
github.com/lestrrat-go/jwx/v3 v3.0.0-alpha1 h1:IKsSdax3m7zsi4ooThn7YR74PMsx8fqcLcEeA6164nI=
github.com/lestrrat-go/jwx/v3 v3.0.0-alpha1/go.mod h1:JLWHVwLtN56LfSrlpyjhvKEdG00MTYOrmzLIJkrCeDw=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
Expand Down Expand Up @@ -1292,6 +1306,8 @@ github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIH
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
Expand Down

0 comments on commit b000a00

Please sign in to comment.