nuclei/pkg/catalog/aws/catalog.go

189 lines
4.6 KiB
Go
Raw Permalink Normal View History

2023-03-23 14:06:54 -05:00
package aws
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"path"
"slices"
2023-03-23 14:06:54 -05:00
"strings"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
// Catalog manages the AWS S3 template catalog
type Catalog struct {
svc client
}
// client interface abstracts S3 connections
type client interface {
getAllKeys() ([]string, error)
downloadKey(name string) (io.ReadCloser, error)
setBucket(bucket string)
}
type s3svc struct {
client *s3.Client
bucket string
}
// NewCatalog creates a new AWS Catalog object given a required S3 bucket name and optional configurations. If
// no configurations to set AWS keys are provided then environment variables will be used to obtain AWS credentials.
func NewCatalog(bucket string, configurations ...func(*Catalog) error) (Catalog, error) {
var c Catalog
for _, configuration := range configurations {
err := configuration(&c)
if err != nil {
return c, err
}
}
if c.svc == nil {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return c, err
}
c.svc = &s3svc{
client: s3.NewFromConfig(cfg),
}
}
c.svc.setBucket(bucket)
return c, nil
}
// WithAWSKeys enables explicitly setting the AWS access key, secret key and region
func WithAWSKeys(accessKey, secretKey, region string) func(*Catalog) error {
return func(c *Catalog) error {
cfg, err := config.LoadDefaultConfig(context.TODO(),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")),
config.WithRegion(region))
if err != nil {
return err
}
c.svc = &s3svc{
client: s3.NewFromConfig(cfg),
bucket: "",
}
return nil
}
}
// OpenFile downloads a file from S3 and returns the contents as an io.ReadCloser
func (c Catalog) OpenFile(filename string) (io.ReadCloser, error) {
if filename == "" {
return nil, errors.New("empty filename")
}
return c.svc.downloadKey(filename)
}
// GetTemplatePath looks for a target string performing a simple substring check
// against all S3 keys. If the input includes a wildcard (*) it is removed.
func (c Catalog) GetTemplatePath(target string) ([]string, error) {
target = strings.ReplaceAll(target, "*", "")
keys, err := c.svc.getAllKeys()
if err != nil {
return nil, err
}
var matches []string
for _, key := range keys {
if strings.Contains(key, target) {
matches = append(matches, key)
}
}
return matches, nil
}
// GetTemplatesPath returns all templates from S3
func (c Catalog) GetTemplatesPath(definitions []string) ([]string, map[string]error) {
keys, err := c.svc.getAllKeys()
if err != nil {
// necessary to implement the Catalog interface
return nil, map[string]error{"aws": err}
}
return keys, nil
}
// ResolvePath gets a full S3 key given the first param. If the second parameter is
// provided it tries to find paths relative to the second path.
func (c Catalog) ResolvePath(templateName, second string) (string, error) {
keys, err := c.svc.getAllKeys()
if err != nil {
return "", err
}
// if c second path is given, it's c folder and we join the two and check against keys
if second != "" {
Spelling (#4008) * spelling: addresses Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: asynchronous Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: basic Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: brute force Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: constant Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: disables Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: engine Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: every time Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: execution Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: false positives Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: from Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: further Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: github Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: gitlab Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: highlight Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: hygiene Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: ignore Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: input Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: item Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: itself Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: latestxxx Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: navigation Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: negative Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: nonexistent Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: occurred Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: override Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: overrides Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: payload Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: performed Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: respective Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: retrieve Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: scanlist Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: separated Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: separator Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: severity Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: source Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: strategy Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: string Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: templates Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: terminal Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: timeout Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: trailing slash Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: trailing Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: websocket Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --------- Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-08-01 14:33:43 -04:00
// Note: Do not replace `path` with `filepath` since filepath is aware of Os path separator
// and we only see `/` in s3 paths changing it to filepath cause build fail and other errors
target := path.Join(path.Dir(second), templateName)
2023-03-23 14:06:54 -05:00
for _, key := range keys {
if key == target {
return key, nil
}
}
}
// check if templateName is already an absolute path to c key
if slices.Contains(keys, templateName) {
return templateName, nil
2023-03-23 14:06:54 -05:00
}
2023-03-27 21:23:18 +05:30
return "", fmt.Errorf("no such path found: %s%s for keys: %v", second, templateName, keys)
2023-03-23 14:06:54 -05:00
}
func (s *s3svc) getAllKeys() ([]string, error) {
paginator := s3.NewListObjectsV2Paginator(s.client, &s3.ListObjectsV2Input{
Bucket: &s.bucket,
})
var keys []string
for paginator.HasMorePages() {
page, err := paginator.NextPage(context.TODO())
if err != nil {
return nil, err
}
for _, obj := range page.Contents {
key := aws.ToString(obj.Key)
keys = append(keys, key)
}
}
return keys, nil
}
func (s *s3svc) downloadKey(name string) (io.ReadCloser, error) {
downloader := manager.NewDownloader(s.client)
buf := manager.NewWriteAtBuffer([]byte{})
_, err := downloader.Download(context.TODO(), buf, &s3.GetObjectInput{
Bucket: aws.String(s.bucket),
Key: aws.String(name),
})
if err != nil {
return nil, err
}
return io.NopCloser(bytes.NewReader(buf.Bytes())), nil
}
func (s *s3svc) setBucket(bucket string) {
s.bucket = bucket
}