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

[v9] Database agents to share same IAM policy (#11320) #12457

Merged
merged 3 commits into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
24 changes: 14 additions & 10 deletions api/types/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -511,18 +511,22 @@ func (d *DatabaseV3) GetIAMAction() string {
func (d *DatabaseV3) GetIAMResources() []string {
aws := d.GetAWS()
if d.IsRDS() {
return []string{
fmt.Sprintf("arn:aws:rds-db:%v:%v:dbuser:%v/*",
aws.Region, aws.AccountID, aws.RDS.ResourceID),
if aws.Region != "" && aws.AccountID != "" && aws.RDS.ResourceID != "" {
return []string{
fmt.Sprintf("arn:aws:rds-db:%v:%v:dbuser:%v/*",
aws.Region, aws.AccountID, aws.RDS.ResourceID),
}
}
} else if d.IsRedshift() {
return []string{
fmt.Sprintf("arn:aws:redshift:%v:%v:dbuser:%v/*",
aws.Region, aws.AccountID, aws.Redshift.ClusterID),
fmt.Sprintf("arn:aws:redshift:%v:%v:dbname:%v/*",
aws.Region, aws.AccountID, aws.Redshift.ClusterID),
fmt.Sprintf("arn:aws:redshift:%v:%v:dbgroup:%v/*",
aws.Region, aws.AccountID, aws.Redshift.ClusterID),
if aws.Region != "" && aws.AccountID != "" && aws.Redshift.ClusterID != "" {
return []string{
fmt.Sprintf("arn:aws:redshift:%v:%v:dbuser:%v/*",
aws.Region, aws.AccountID, aws.Redshift.ClusterID),
fmt.Sprintf("arn:aws:redshift:%v:%v:dbname:%v/*",
aws.Region, aws.AccountID, aws.Redshift.ClusterID),
fmt.Sprintf("arn:aws:redshift:%v:%v:dbgroup:%v/*",
aws.Region, aws.AccountID, aws.Redshift.ClusterID),
}
}
}
return nil
Expand Down
36 changes: 17 additions & 19 deletions lib/services/semaphore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,60 +17,58 @@ limitations under the License.
package services

import (
"testing"
"time"

"github.com/gravitational/teleport/api/types"
"gopkg.in/check.v1"
)

type SemaphoreSuite struct{}

var _ = check.Suite(&SemaphoreSuite{})
"github.com/stretchr/testify/require"
)

func (s *SemaphoreSuite) TestAcquireSemaphoreRequest(c *check.C) {
func TestAcquireSemaphoreRequest(t *testing.T) {
ok := types.AcquireSemaphoreRequest{
SemaphoreKind: "foo",
SemaphoreName: "bar",
MaxLeases: 1,
Expires: time.Now(),
}
ok2 := ok
c.Assert(ok.Check(), check.IsNil)
c.Assert(ok2.Check(), check.IsNil)
require.Nil(t, ok.Check())
require.Nil(t, ok2.Check())

// Check that all the required fields have their
// zero values rejected.
bad := ok
bad.SemaphoreKind = ""
c.Assert(bad.Check(), check.NotNil)
require.NotNil(t, bad.Check())
bad = ok
bad.SemaphoreName = ""
c.Assert(bad.Check(), check.NotNil)
require.NotNil(t, bad.Check())
bad = ok
bad.MaxLeases = 0
c.Assert(bad.Check(), check.NotNil)
require.NotNil(t, bad.Check())
bad = ok
bad.Expires = time.Time{}
c.Assert(bad.Check(), check.NotNil)
require.NotNil(t, bad.Check())

// ensure that well formed acquire params can configure
// a well formed semaphore.
sem, err := ok.ConfigureSemaphore()
c.Assert(err, check.IsNil)
require.NoError(t, err)

// verify acquisition works and semaphore state is
// correctly updated.
lease, err := sem.Acquire("sem-id", ok)
c.Assert(err, check.IsNil)
c.Assert(sem.Contains(*lease), check.Equals, true)
require.NoError(t, err)
require.True(t, sem.Contains(*lease))

// verify keepalive succeeds and correctly updates
// semaphore expiry.
newLease := *lease
newLease.Expires = sem.Expiry().Add(time.Second)
c.Assert(sem.KeepAlive(newLease), check.IsNil)
c.Assert(sem.Expiry(), check.Equals, newLease.Expires)
require.Nil(t, sem.KeepAlive(newLease))
require.Equal(t, newLease.Expires, sem.Expiry())

c.Assert(sem.Cancel(newLease), check.IsNil)
c.Assert(sem.Contains(newLease), check.Equals, false)
require.Nil(t, sem.Cancel(newLease))
require.False(t, sem.Contains(newLease))
}
40 changes: 22 additions & 18 deletions lib/srv/db/cloud/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package cloud
import (
"context"
"encoding/json"
"fmt"

"github.com/gravitational/teleport/api/types"
awslib "github.com/gravitational/teleport/lib/cloud/aws"
Expand All @@ -43,8 +42,8 @@ type awsConfig struct {
identity awslib.Identity
// database is the database instance to configure.
database types.Database
// hostID is the host identifier where this agent's running.
hostID string
// policyName is the name of the inline policy for the identity.
policyName string
}

// Check validates the config.
Expand All @@ -58,8 +57,8 @@ func (c *awsConfig) Check() error {
if c.database == nil {
return trace.BadParameter("missing parameter database")
}
if c.hostID == "" {
return trace.BadParameter("missing parameter host ID")
if c.policyName == "" {
return trace.BadParameter("missing parameter policy name")
}
return nil
}
Expand Down Expand Up @@ -169,13 +168,18 @@ func (r *awsClient) enableIAMAuth(ctx context.Context) error {

// ensureIAMPolicy adds database connect permissions to the agent's policy.
func (r *awsClient) ensureIAMPolicy(ctx context.Context) error {
resources := r.cfg.database.GetIAMResources()
if len(resources) == 0 {
return nil
}

policy, err := r.getIAMPolicy(ctx)
if err != nil {
return trace.Wrap(err)
}
action := r.cfg.database.GetIAMAction()
var changed bool
for _, resource := range r.cfg.database.GetIAMResources() {
for _, resource := range resources {
if policy.Ensure(awslib.EffectAllow, action, resource) {
r.log.Debugf("Permission %q for %q is already part of policy.", action, resource)
} else {
Expand All @@ -195,12 +199,17 @@ func (r *awsClient) ensureIAMPolicy(ctx context.Context) error {

// deleteIAMPolicy deletes IAM access policy from the identity this agent is running as.
func (r *awsClient) deleteIAMPolicy(ctx context.Context) error {
resources := r.cfg.database.GetIAMResources()
if len(resources) == 0 {
return nil
}

policy, err := r.getIAMPolicy(ctx)
if err != nil {
return trace.Wrap(err)
}
action := r.cfg.database.GetIAMAction()
for _, resource := range r.cfg.database.GetIAMResources() {
for _, resource := range resources {
policy.Delete(awslib.EffectAllow, action, resource)
}
// If policy is empty now, delete it as IAM policy can't be empty.
Expand All @@ -216,7 +225,7 @@ func (r *awsClient) getIAMPolicy(ctx context.Context) (*awslib.PolicyDocument, e
switch r.cfg.identity.(type) {
case awslib.Role:
out, err := r.iam.GetRolePolicyWithContext(ctx, &iam.GetRolePolicyInput{
PolicyName: aws.String(r.policyName()),
PolicyName: aws.String(r.cfg.policyName),
RoleName: aws.String(r.cfg.identity.GetName()),
})
if err != nil {
Expand All @@ -228,7 +237,7 @@ func (r *awsClient) getIAMPolicy(ctx context.Context) (*awslib.PolicyDocument, e
policyDocument = aws.StringValue(out.PolicyDocument)
case awslib.User:
out, err := r.iam.GetUserPolicyWithContext(ctx, &iam.GetUserPolicyInput{
PolicyName: aws.String(r.policyName()),
PolicyName: aws.String(r.cfg.policyName),
UserName: aws.String(r.cfg.identity.GetName()),
})
if err != nil {
Expand All @@ -254,13 +263,13 @@ func (r *awsClient) updateIAMPolicy(ctx context.Context, policy *awslib.PolicyDo
switch r.cfg.identity.(type) {
case awslib.Role:
_, err = r.iam.PutRolePolicyWithContext(ctx, &iam.PutRolePolicyInput{
PolicyName: aws.String(r.policyName()),
PolicyName: aws.String(r.cfg.policyName),
PolicyDocument: aws.String(string(document)),
RoleName: aws.String(r.cfg.identity.GetName()),
})
case awslib.User:
_, err = r.iam.PutUserPolicyWithContext(ctx, &iam.PutUserPolicyInput{
PolicyName: aws.String(r.policyName()),
PolicyName: aws.String(r.cfg.policyName),
PolicyDocument: aws.String(string(document)),
UserName: aws.String(r.cfg.identity.GetName()),
})
Expand All @@ -277,21 +286,16 @@ func (r *awsClient) detachIAMPolicy(ctx context.Context) error {
switch r.cfg.identity.(type) {
case awslib.Role:
_, err = r.iam.DeleteRolePolicyWithContext(ctx, &iam.DeleteRolePolicyInput{
PolicyName: aws.String(r.policyName()),
PolicyName: aws.String(r.cfg.policyName),
RoleName: aws.String(r.cfg.identity.GetName()),
})
case awslib.User:
_, err = r.iam.DeleteUserPolicyWithContext(ctx, &iam.DeleteUserPolicyInput{
PolicyName: aws.String(r.policyName()),
PolicyName: aws.String(r.cfg.policyName),
UserName: aws.String(r.cfg.identity.GetName()),
})
default:
return trace.BadParameter("can only detach policies from roles or users, got %v", r.cfg.identity)
}
return common.ConvertError(err)
}

// policyName is the inline IAM policy name this agent is managing.
func (r *awsClient) policyName() string {
return fmt.Sprintf("teleport-%v", r.cfg.hostID)
}
Loading