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

feat: add validation for secret block #126

Merged
merged 28 commits into from
Jan 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4ef5709
refactor: swap yaml libraries
Dec 2, 2020
b9513ab
refactor(raw): update library in package
Dec 2, 2020
cfb79ab
refactor(library): update library in package
Dec 2, 2020
f2047c3
fix: add test back
Dec 2, 2020
80f6993
refactor: move linting from compiler to types
Dec 10, 2020
a2acbb0
feat: add validation for secret block
Dec 29, 2020
1dfdb94
fix: update merge conflicts
Dec 29, 2020
e53746f
feat: add linting for shared secrets and plugins
Dec 30, 2020
f0fbcfe
Merge branch 'master' into pkg/yaml
Dec 30, 2020
cf304f0
fix: remove dead var
Dec 30, 2020
a5b3661
Merge branch 'pkg/yaml' of github.com:go-vela/types into pkg/yaml
Dec 30, 2020
2f4df09
fix: update line length issues
Dec 30, 2020
41d4f9e
fix: dupl linter errors
Dec 30, 2020
0254170
fix: steps typos
Jan 4, 2021
b8b1751
fix: swap out linter check
Jan 4, 2021
bae84d6
fix: add bad annotation tests
Jan 4, 2021
0e76db6
fix: return primitive bool value
Jan 4, 2021
99e3fd9
Merge branch 'master' into pkg/yaml
Jan 4, 2021
798d5c5
refactor: move engine check into validate func
Jan 4, 2021
ef663d2
Merge branch 'master' into pkg/yaml
Jan 5, 2021
5e73730
fix: unify line formatting for invalid errors
Jan 5, 2021
8a4634d
fix: add no lint comment
Jan 6, 2021
c605557
Merge branch 'master' into pkg/yaml
jbrockopp Jan 6, 2021
ca79db9
Merge branch 'master' into pkg/yaml
jbrockopp Jan 7, 2021
455a6fc
Merge branch 'master' into pkg/yaml
wass3r Jan 11, 2021
bc476cf
fix: update linter line length issues
Jan 11, 2021
71281c1
Merge branch 'master' of github.com:go-vela/types into pkg/yaml
Jan 11, 2021
20c4179
Merge branch 'pkg/yaml' of github.com:go-vela/types into pkg/yaml
Jan 11, 2021
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
8 changes: 7 additions & 1 deletion yaml/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,14 @@ func (b *Build) Validate(pipeline []byte) error {
invalid = fmt.Errorf("%w: %s", invalid, "stages and steps provided")
}

// validate the secrets block provided
err := b.Secrets.Validate(pipeline)
if err != nil {
invalid = fmt.Errorf("%v: %w", invalid, err)
}

// validate the services block provided
err := b.Services.Validate(pipeline)
err = b.Services.Validate(pipeline)
if err != nil {
invalid = fmt.Errorf("%v: %w", invalid, err)
}
Expand Down
276 changes: 276 additions & 0 deletions yaml/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ package yaml
import (
"errors"
"fmt"
"regexp"
"strings"

"github.com/docker/distribution/reference"
"github.com/go-vela/types/constants"
"github.com/go-vela/types/pipeline"
"github.com/go-vela/types/raw"
"github.com/goccy/go-yaml"
)

// nolint:lll // jsonschema will cause long lines
Expand Down Expand Up @@ -221,3 +224,276 @@ func (s *StepSecretSlice) UnmarshalYAML(unmarshal func(interface{}) error) error

return errors.New("failed to unmarshal StepSecretSlice")
}

// Validate lints if the secrets configuration is valid.
func (s *SecretSlice) Validate(pipeline []byte) error {
invalid, isInvalid := errors.New("invalid secret block found"), false

// iterate through each secret and linting yaml tags
for i, secret := range *s {
// check required name field
if len(secret.Name) == 0 && secret.Origin.Empty() {
path, err := yaml.PathString(fmt.Sprintf("$.secrets[%d]", i))
if err != nil {
return fmt.Errorf("failed compile: unable to path index: %w", err)
}

source, err := path.AnnotateSource(pipeline, true)
if err != nil {
return fmt.Errorf("failed compile: unable to annotate: %w", err)
}

invalid = fmt.Errorf("%w: %s", invalid,
fmt.Sprintf("no name provided:\n%s\n ", string(source)))
isInvalid = true
}

// check if the engine is not a "native" or "vault"
if len(secret.Engine) != 0 {
if !strings.EqualFold(secret.Engine, constants.DriverNative) &&
!strings.EqualFold(secret.Engine, constants.DriverVault) {
path, err := yaml.PathString(fmt.Sprintf("$.secrets[%d].engine", i))
if err != nil {
return fmt.Errorf("failed compile: unable to path index: %w", err)
}

source, err := path.AnnotateSource(pipeline, true)
if err != nil {
return fmt.Errorf("failed compile: unable to annotate: %w", err)
}

invalid = fmt.Errorf("%w: %s", invalid,
fmt.Sprintf("invalid engine value:\n%s\n ", string(source)))
isInvalid = true
}
}

// allocate variables for secret type checks
var (
bad bool
err error
)

// validate secret by type
switch {
case strings.EqualFold(secret.Type, constants.SecretRepo):
bad, err = secret.validateRepo(pipeline, i)
case strings.EqualFold(secret.Type, constants.SecretOrg):
bad, err = secret.validateOrg(pipeline, i)
case strings.EqualFold(secret.Type, constants.SecretShared):
bad, err = secret.validateShared(pipeline, i)
case !secret.Origin.Empty():
bad, err = secret.validatePlugin(pipeline, i)
}

// check if we need to append a user yaml error
if bad {
invalid = fmt.Errorf("%v: %v", invalid, err)
isInvalid = true
}

// check if the compiler has failed from bad yaml
if strings.HasPrefix(err.Error(), "failed compile:") {
return err
}
}

// check if only default error exists
if isInvalid {
return invalid
}

return nil
}

// validateRepo is a helper function to lint secrets of type "repo".
//
// this function is used to check the fields of secret with the explicit
// definition yaml style.
func (s *Secret) validateRepo(pipeline []byte, i int) (bool, error) {
invalid, isInvalid := errors.New("invalid secret"), false

// check if a key was provided for explicit definition
// when the key == name than we have an implicit definition
if len(s.Key) != 0 && s.Key != s.Name {
match, err := regexp.MatchString(`.+\/.+\/.+`, s.Key)
if err != nil {
return false, fmt.Errorf("unable to execute regex on %s: %w", s.Key, err)
}

// provide anotated error message when bad syntax is detected
if !match {
path, err := yaml.PathString(fmt.Sprintf("$.secrets[%d].key", i))
if err != nil {
return false, fmt.Errorf("failed compile: unable to path index: %w", err)
}

source, err := path.AnnotateSource(pipeline, true)
if err != nil {
return false, fmt.Errorf("failed compile: unable to annotate: %w", err)
}

invalid = fmt.Errorf("%w: %s", invalid,
fmt.Sprintf("invalid key value:\n%s\n ", string(source)))
isInvalid = true
}
}

return isInvalid, invalid
}

// validateOrg is a helper function to lint secrets of type "org".
// nolint:dupl // ignoring dupl to make clearly define which function owns linting a secret type
func (s *Secret) validateOrg(pipeline []byte, i int) (bool, error) {
invalid, isInvalid := errors.New("invalid secret"), false

// check if a key was provided
match, err := regexp.MatchString(`.+\/.+`, s.Key)
if err != nil {
return false, fmt.Errorf("unable to execute regex on %s: %w", s.Key, err)
}

// provide anotated error message when bad syntax is detected
if !match {
if strings.EqualFold(s.Name, s.Key) {
path, err := yaml.PathString(fmt.Sprintf("$.secrets[%d]", i))
if err != nil {
return false, fmt.Errorf("failed compile: unable to path index: %w", err)
}

source, err := path.AnnotateSource(pipeline, true)
if err != nil {
return false, fmt.Errorf("failed compile: unable to annotate: %w", err)
}

invalid = fmt.Errorf("%v: %s", invalid,
fmt.Sprintf("no key provided:\n%s\n ", string(source)))
isInvalid = true
} else {
path, err := yaml.PathString(fmt.Sprintf("$.secrets[%d].key", i))
if err != nil {
return false, fmt.Errorf("failed compile: unable to path index: %w", err)
}

source, err := path.AnnotateSource(pipeline, true)
if err != nil {
return false, fmt.Errorf("failed compile: unable to annotate: %w", err)
}

invalid = fmt.Errorf("%v: %s", invalid,
fmt.Sprintf("invalid key value:\n%s\n ", string(source)))
isInvalid = true
}
}

return isInvalid, invalid
}

// validateShared is a helper function to lint secrets of type "shared".
// nolint:dupl // ignoring dupl to make clearly define which function owns linting a secret type
func (s *Secret) validateShared(pipeline []byte, i int) (bool, error) {
invalid, isInvalid := errors.New("invalid secret"), false

// check if a key was provided
match, err := regexp.MatchString(`.+\/.+\/.+`, s.Key)
if err != nil {
return false, fmt.Errorf("unable to execute regex on %s: %w", s.Key, err)
}

// provide anotated error message when bad syntax is detected
if !match {
if strings.EqualFold(s.Name, s.Key) {
path, err := yaml.PathString(fmt.Sprintf("$.secrets[%d]", i))
if err != nil {
return false, fmt.Errorf("failed compile: unable to path index: %w", err)
}

source, err := path.AnnotateSource(pipeline, true)
if err != nil {
return false, fmt.Errorf("failed compile: unable to annotate: %w", err)
}

invalid = fmt.Errorf("%v: %s", invalid,
fmt.Sprintf("no key provided:\n%s\n ", string(source)))
isInvalid = true
} else {
path, err := yaml.PathString(fmt.Sprintf("$.secrets[%d].key", i))
if err != nil {
return false, fmt.Errorf("failed compile: unable to path index: %w", err)
}

source, err := path.AnnotateSource(pipeline, true)
if err != nil {
return false, fmt.Errorf("failed compile: unable to annotate: %w", err)
}

invalid = fmt.Errorf("%v: %s", invalid,
fmt.Sprintf("invalid key value:\n%s\n ", string(source)))
isInvalid = true
}
}

return isInvalid, invalid
}

// validatePlugin is a helper function to lint secret plugin fields.
func (s *Secret) validatePlugin(pipeline []byte, i int) (bool, error) {
invalid, isInvalid := errors.New("invalid secret plugin"), false

// check required fields
if len(s.Origin.Name) == 0 {
path, err := yaml.PathString(fmt.Sprintf("$.secrets[%d]", i))
if err != nil {
return false, fmt.Errorf("failed compile: unable to path index: %w", err)
}

source, err := path.AnnotateSource(pipeline, true)
if err != nil {
return false, fmt.Errorf("failed compile: unable to annotate: %w", err)
}

invalid = fmt.Errorf("%w: %s", invalid,
fmt.Sprintf("no name provided:\n%s\n ", string(source)))
isInvalid = true
}

if len(s.Origin.Image) == 0 {
path, err := yaml.PathString(fmt.Sprintf("$.secrets[%d]", i))
if err != nil {
return false, fmt.Errorf("failed compile: unable to path index: %w", err)
}

source, err := path.AnnotateSource(pipeline, true)
if err != nil {
return false, fmt.Errorf("failed compile: unable to annotate: %w", err)
}

invalid = fmt.Errorf("%w: %s", invalid,
fmt.Errorf("no image provided %s:\n%s\n ", s.Origin.Name, string(source)))
isInvalid = true
} else {
// parse the image provided into a
// named, fully qualified reference
//
// https://pkg.go.dev/github.com/docker/distribution/reference?tab=doc#ParseAnyReference
_, err := reference.ParseAnyReference(s.Origin.Image)
if err != nil {
// output error with YAML source
path, err := yaml.PathString(fmt.Sprintf("$.secrets[%d].origin.image", i))
if err != nil {
return false, fmt.Errorf("failed compile: unable to path index: %w", err)
}

source, err := path.AnnotateSource(pipeline, true)
if err != nil {
return false, fmt.Errorf("failed compile: unable to annotate: %w", err)
}

invalid = fmt.Errorf("%w: %s", invalid,
fmt.Errorf("invalid image value %s:\n%s\n ", s.Origin.Image, string(source)))
isInvalid = true
}
}

return isInvalid, invalid
}
Loading