Skip to content
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
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@ require (
github.com/aws/aws-sdk-go-v2/service/ec2 v1.100.1
github.com/aws/aws-sdk-go-v2/service/ecs v1.27.1
github.com/aws/aws-sdk-go-v2/service/glue v1.51.0
github.com/aws/aws-sdk-go-v2/service/iam v1.20.3
github.com/aws/aws-sdk-go-v2/service/rds v1.44.1
github.com/aws/aws-sdk-go-v2/service/s3 v1.35.0
github.com/aws/aws-sdk-go-v2/service/sns v1.20.13
github.com/aws/aws-sdk-go-v2/service/sqs v1.22.0
github.com/aws/aws-sdk-go-v2/service/sts v1.19.2
github.com/aws/aws-sigv4-auth-cassandra-gocql-driver-plugin v0.0.0-20220331165046-e4d000c0d6a6
github.com/aws/smithy-go v1.13.5
github.com/beevik/etree v1.2.0
github.com/bufbuild/connect-go v1.7.0
github.com/buildkite/bintest/v3 v3.1.1
Expand Down Expand Up @@ -229,7 +231,6 @@ require (
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.12 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/boombuler/barcode v1.0.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ github.com/aws/aws-sdk-go-v2/service/ecs v1.27.1 h1:54QSuWR3Pot7HqBRXd+c1yF97h2b
github.com/aws/aws-sdk-go-v2/service/ecs v1.27.1/go.mod h1:SB6YszwN1iKvyt/Qk+ICeKsfBxjd0CTEwwkmej9qoa0=
github.com/aws/aws-sdk-go-v2/service/glue v1.51.0 h1:ezO4k5Nnowpedvk1LGtYQUWiXaWx+zq+CCmbcTcZBoI=
github.com/aws/aws-sdk-go-v2/service/glue v1.51.0/go.mod h1:wMCE0B6l8eHb57l2DMYCGxt0rHIfcu3RvIY7SAfc+Fs=
github.com/aws/aws-sdk-go-v2/service/iam v1.20.3 h1:oO895XrrD1khVUv0fUFTpbvCK+/IS9nnlhie5WYXPgw=
github.com/aws/aws-sdk-go-v2/service/iam v1.20.3/go.mod h1:aQZ8BI+reeaY7RI/QQp7TKCSUHOesTdrzzylp3CW85c=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28/go.mod h1:spfrICMD6wCAhjhzHuy6DOZZ+LAIY10UxhUmLzpJTTs=
Expand Down
39 changes: 37 additions & 2 deletions lib/cloud/aws/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"errors"
"net/http"

awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/gravitational/trace"
Expand All @@ -34,7 +36,11 @@ func ConvertRequestFailureError(err error) error {
return err
}

switch requestErr.StatusCode() {
return convertRequestFailureErrorFromStatusCode(requestErr.StatusCode(), requestErr)
}

func convertRequestFailureErrorFromStatusCode(statusCode int, requestErr error) error {
switch statusCode {
case http.StatusForbidden:
return trace.AccessDenied(requestErr.Error())
case http.StatusConflict:
Expand All @@ -43,7 +49,7 @@ func ConvertRequestFailureError(err error) error {
return trace.NotFound(requestErr.Error())
}

return err // Return unmodified.
return requestErr // Return unmodified.
}

// ConvertIAMError converts common errors from IAM clients to trace errors.
Expand Down Expand Up @@ -79,3 +85,32 @@ func parseMetadataClientError(err error) error {
}
return trace.Wrap(err)
}

// ConvertIAMv2Error converts common errors from IAM clients to trace errors.
func ConvertIAMv2Error(err error) error {
if err == nil {
return nil
}

var entityExistsError *iamTypes.EntityAlreadyExistsException
if errors.As(err, &entityExistsError) {
return trace.AlreadyExists(*entityExistsError.Message)
}

var entityNotFound *iamTypes.NoSuchEntityException
if errors.As(err, &entityNotFound) {
return trace.NotFound(*entityNotFound.Message)
}

var malformedPolicyDocument *iamTypes.MalformedPolicyDocumentException
if errors.As(err, &malformedPolicyDocument) {
return trace.BadParameter(*malformedPolicyDocument.Message)
}

var re *awshttp.ResponseError
if errors.As(err, &re) {
return convertRequestFailureErrorFromStatusCode(re.HTTPStatusCode(), re.Err)
}

return err
}
116 changes: 116 additions & 0 deletions lib/cloud/aws/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
Copyright 2023 Gravitational, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package aws

import (
"net/http"
"testing"

awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
"github.com/aws/aws-sdk-go/aws"
smithyhttp "github.com/aws/smithy-go/transport/http"
"github.com/gravitational/trace"
"github.com/stretchr/testify/require"
)

func TestConvertIAMv2Error(t *testing.T) {
for _, tt := range []struct {
name string
inErr error
errCheck require.ErrorAssertionFunc
}{
{
name: "no error",
inErr: nil,
errCheck: require.NoError,
},
{
name: "resource already exists",
inErr: &iamTypes.EntityAlreadyExistsException{
Message: aws.String("resource exists"),
},
errCheck: func(tt require.TestingT, err error, i ...interface{}) {
require.True(tt, trace.IsAlreadyExists(err), "expected trace.AlreadyExists error, got %v", err)
},
},
{
name: "resource already exists",
inErr: &iamTypes.NoSuchEntityException{
Message: aws.String("resource not found"),
},
errCheck: func(tt require.TestingT, err error, i ...interface{}) {
require.True(tt, trace.IsNotFound(err), "expected trace.NotFound error, got %v", err)
},
},
{
name: "invalid policy document",
inErr: &iamTypes.MalformedPolicyDocumentException{
Message: aws.String("malformed document"),
},
errCheck: func(tt require.TestingT, err error, i ...interface{}) {
require.True(tt, trace.IsBadParameter(err), "expected trace.BadParameter error, got %v", err)
},
},
{
name: "unauthorized",
inErr: &awshttp.ResponseError{
ResponseError: &smithyhttp.ResponseError{
Response: &smithyhttp.Response{Response: &http.Response{
StatusCode: http.StatusForbidden,
}},
Err: trace.Errorf(""),
},
},
errCheck: func(tt require.TestingT, err error, i ...interface{}) {
require.True(tt, trace.IsAccessDenied(err), "expected trace.AccessDenied error, got %v", err)
},
},
{
name: "not found",
inErr: &awshttp.ResponseError{
ResponseError: &smithyhttp.ResponseError{
Response: &smithyhttp.Response{Response: &http.Response{
StatusCode: http.StatusNotFound,
}},
Err: trace.Errorf(""),
},
},
errCheck: func(tt require.TestingT, err error, i ...interface{}) {
require.True(tt, trace.IsNotFound(err), "expected trace.NotFound error, got %v", err)
},
},
{
name: "resource already exists",
inErr: &awshttp.ResponseError{
ResponseError: &smithyhttp.ResponseError{
Response: &smithyhttp.Response{Response: &http.Response{
StatusCode: http.StatusConflict,
}},
Err: trace.Errorf(""),
},
},
errCheck: func(tt require.TestingT, err error, i ...interface{}) {
require.True(tt, trace.IsAlreadyExists(err), "expected trace.AlreadyExists error, got %v", err)
},
},
} {
t.Run(tt.name, func(t *testing.T) {
tt.errCheck(t, ConvertIAMv2Error(tt.inErr))
})
}
}
9 changes: 6 additions & 3 deletions lib/cloud/aws/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package aws
import (
"context"
"encoding/json"
"fmt"
"net/url"
"sort"

Expand All @@ -29,6 +28,8 @@ import (
"github.com/gravitational/trace"
log "github.com/sirupsen/logrus"
"golang.org/x/exp/slices"

awsutils "github.com/gravitational/teleport/lib/utils/aws"
)

// Policy represents an AWS IAM policy.
Expand Down Expand Up @@ -72,7 +73,9 @@ type Statement struct {
// Actions is a list of actions.
Actions SliceOrString `json:"Action"`
// Resources is a list of resources.
Resources SliceOrString `json:"Resource"`
Resources SliceOrString `json:"Resource,omitempty"`
// Principals is a list of principals.
Principals map[string]SliceOrString `json:"Principal,omitempty"`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Principals map[string]SliceOrString `json:"Principal,omitempty"`
Principal map[string]SliceOrString `json:"Principal,omitempty"`

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to be consistent with the fields above
They use plural form but then the json tag is singular
Should we change them to singular as well? Resource and Action vs Resources and Actions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I haven't notice this. Lets ignore my comment and keep this constant.

}

// ensureResource ensures that the statement contains the specified resource.
Expand Down Expand Up @@ -318,7 +321,7 @@ func NewPolicies(partitionID string, accountID string, iamClient iamiface.IAMAPI
// * `iam:DeletePolicyVersion`: wildcard ("*") or policy that will be created;
// * `iam:CreatePolicyVersion`: wildcard ("*") or policy that will be created;
func (p *policies) Upsert(ctx context.Context, policy *Policy) (string, error) {
policyARN := fmt.Sprintf("arn:%s:iam::%s:policy/%s", p.partitionID, p.accountID, policy.Name)
policyARN := awsutils.PolicyARN(p.partitionID, p.accountID, policy.Name)
encodedPolicyDocument, err := json.Marshal(policy.Document)
if err != nil {
return "", trace.Wrap(err)
Expand Down
100 changes: 100 additions & 0 deletions lib/cloud/aws/policy_statements.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
Copyright 2021 Gravitational, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package aws

var (
allResources = []string{"*"}
)

// StatementForIAMEditRolePolicy returns a IAM Policy Statement which allows editting Role Policy
// of the resources.
func StatementForIAMEditRolePolicy(resources ...string) *Statement {
return &Statement{
Effect: EffectAllow,
Actions: []string{"iam:GetRolePolicy", "iam:PutRolePolicy", "iam:DeleteRolePolicy"},
Resources: resources,
}
}

// StatementForIAMEditUserPolicy returns a IAM Policy Statement which allows editting User Policy
// of the resources.
func StatementForIAMEditUserPolicy(resources ...string) *Statement {
return &Statement{
Effect: EffectAllow,
Actions: []string{"iam:GetUserPolicy", "iam:PutUserPolicy", "iam:DeleteUserPolicy"},
Resources: resources,
}
}

// StatementForECSManageService returns the statement that allows managing the ECS Service deployed
// by DeployService (AWS OIDC Integration).
func StatementForECSManageService() *Statement {
return &Statement{
Effect: EffectAllow,
Actions: []string{
"ecs:DescribeClusters", "ecs:CreateCluster", "ecs:PutClusterCapacityProviders",
"ecs:DescribeServices", "ecs:CreateService", "ecs:UpdateService",
"ecs:RegisterTaskDefinition",
},
Resources: allResources,
}
}

// StatementForWritingLogs returns the statement that allows the writing logs to CloudWatch.
// This is used by the DeployService (ECS Service) to write teleport logs.
// https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_awslogs.html
func StatementForWritingLogs() *Statement {
return &Statement{
Effect: EffectAllow,
Actions: []string{"logs:CreateLogStream", "logs:PutLogEvents", "logs:CreateLogGroup"},
Resources: allResources,
}
}

// StatementForIAMPassRole returns a statement that allows to iam:PassRole the target role.
// Usage example: when setting up the TaskRole for the ECS Task.
// https://docs.aws.amazon.com/AmazonECS/latest/userguide/task-iam-roles.html#specify-task-iam-roles
func StatementForIAMPassRole(targetRole string) *Statement {
return &Statement{
Effect: EffectAllow,
Actions: SliceOrString{"iam:PassRole"},
Resources: SliceOrString{
targetRole,
},
}
}

// StatementForECSTaskRoleTrustRelationships returns the Trust Relationship to allow the ECS Tasks service to.
// It allows the usage of this Role by the ECS Tasks service.
func StatementForECSTaskRoleTrustRelationships() *Statement {
return &Statement{
Effect: EffectAllow,
Actions: SliceOrString{"sts:AssumeRole"},
Principals: map[string]SliceOrString{
"Service": {"ecs-tasks.amazonaws.com"},
},
}
}

// StatementForRDSDBConnect returns a statement that allows the `rds-db:connect` for all RDS DBs.
func StatementForRDSDBConnect() *Statement {
return &Statement{
Effect: EffectAllow,
Actions: SliceOrString{"rds-db:connect"},
Resources: allResources,
}
}
Comment on lines +93 to +100
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there plan to support other DB types of deploy service? if so, ideally can share this with the db actions listed in configurators/aws. I guess we can refactor when that days comes too =D.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will probably have more in the future 👍

There's indeed some duplication that we should get rid off.
Let's see how the DeployService evolves and then centralize all these statements.

Loading