From 307c48eab413535f2a028fb30728e0b8f276d10d Mon Sep 17 00:00:00 2001 From: Nico Esteves Date: Thu, 20 Dec 2018 15:42:37 +0100 Subject: [PATCH] Added iam-sync-users --- CHANGELOG.md | 6 +++ README.md | 1 + VERSION | 2 +- iam/sync-users/README.md | 46 ++++++++++++++++++ iam/sync-users/main.go | 100 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 iam/sync-users/README.md create mode 100644 iam/sync-users/main.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 2669128..112c3f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v6.1.0 (21/12/2018) + +**New** + +* Added `iam-sync-users` + ## v6.0.0 (17/12/2018) **Fix** diff --git a/README.md b/README.md index 5d71c1b..12ee18d 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Collection of tools to make working with AWS a bit easier without having to depe | [iam-session](iam/session/) | Creates new IAM session with role assumption and MFA support. | | [iam-public-keys](iam/public-ssh-keys) | Returns the public SSH keys of an IAM user. | | [iam-auth-proxy](iam/auth-proxy) | Use IAM as identity provider for services. | +| [iam-sync-users](iam/sync-users) | Create Linux users from IAM | | [cloudwatch-put-metric-data](cloudwatch/put-metric-data) | Basic sending a metric value to cloudwatch | | [ec2-ip-from-name](ec2/ip-from-name) | Given an EC2 name, list up to `-max-results` IPs associated with instances with that name | | [ecr-get-login](ecr/get-login) | Prints out the command to run to auth with docker ECR. Check output flag for other options | diff --git a/VERSION b/VERSION index 09b254e..dfda3e0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.0.0 +6.1.0 diff --git a/iam/sync-users/README.md b/iam/sync-users/README.md new file mode 100644 index 0000000..24ad4e7 --- /dev/null +++ b/iam/sync-users/README.md @@ -0,0 +1,46 @@ +# iam-sync-users + +``` +Note: Only works on Linux with sudo +``` + +``` +usage: iam-sync-users [] + +Create users from IAM + +Flags: + --help Show context-sensitive help (also try --help-long and --help-man). + --assume-role-arn=ASSUME-ROLE-ARN + Role to assume + --assume-role-external-id=ASSUME-ROLE-EXTERNAL-ID + External ID of the role to assume + --assume-role-session-name=ASSUME-ROLE-SESSION-NAME + Role session name + --region=REGION AWS Region + --mfa-serial-number=MFA-SERIAL-NUMBER + MFA Serial Number + --mfa-token-code=MFA-TOKEN-CODE + MFA Token Code + -v, --version Display the version + --group=GROUP ... Add users from this group. You can use --group multiple times. +``` + +## IAM policy + +You can use the `arn:aws:iam::aws:policy/IAMReadOnlyAccess` managed policy or use the custom one below + +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "iam:GetGroup" + ], + "Resource": "*" + } + ] +} +``` diff --git a/iam/sync-users/main.go b/iam/sync-users/main.go new file mode 100644 index 0000000..088ae4e --- /dev/null +++ b/iam/sync-users/main.go @@ -0,0 +1,100 @@ +package main + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "os/exec" + "os/user" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/iam" + "github.com/hamstah/awstools/common" + + kingpin "gopkg.in/alecthomas/kingpin.v2" +) + +var ( + flags = common.KingpinSessionFlags() + infoFlags = common.KingpinInfoFlags() + groups = kingpin.Flag("group", "Add users from this group. You can use --group multiple times.").Strings() +) + +func main() { + kingpin.CommandLine.Name = "iam-sync-users" + kingpin.CommandLine.Help = "Create users from IAM" + kingpin.Parse() + common.HandleInfoFlags(infoFlags) + common.FatalOnError(ensureCanCreateUser()) + + session := session.Must(session.NewSession()) + conf := common.AssumeRoleConfig(flags, session) + + iamClient := iam.New(session, conf) + + users := []string{} + + for _, group := range *groups { + newUsers, err := getUsersForGroup(iamClient, group) + common.FatalOnError(err) + users = append(users, newUsers...) + } + + for _, userName := range users { + _, err := user.Lookup(userName) + if err == nil { + // user already exists + continue + } + + common.FatalOnError(createUser(userName)) + fmt.Println(userName) + } +} + +func createUser(userName string) error { + cmd := exec.Command("/usr/sbin/adduser", userName) + err := cmd.Run() + if err != nil { + return err + } + + sudoFilename := fmt.Sprintf("/etc/sudoers.d/%s", strings.Replace(userName, ".", "", -1)) + + err = ioutil.WriteFile(sudoFilename, []byte(fmt.Sprintf("%s ALL=(ALL) NOPASSWD:ALL\n", userName)), 0644) + if err != nil { + return err + } + + return nil +} + +func ensureCanCreateUser() error { + if _, err := os.Stat("/usr/sbin/adduser"); os.IsNotExist(err) { + return errors.New("Can't find adduser to create user") + } + + if _, err := os.Stat("/etc/sudoers.d"); os.IsNotExist(err) { + return errors.New("Can't find adduser to create user") + } + return nil +} + +func getUsersForGroup(client *iam.IAM, groupName string) ([]string, error) { + users := []string{} + err := client.GetGroupPages(&iam.GetGroupInput{GroupName: aws.String(groupName)}, + func(page *iam.GetGroupOutput, lastPage bool) bool { + for _, user := range page.Users { + users = append(users, *user.UserName) + } + return true + }) + + if err != nil { + return nil, err + } + return users, nil +}