Managing Github With Terraform and Saml Without Github Enterprise

Posted on May 17, 2022

Summary

At Hippo, we have dozens of engineers across teams, with very different access patterns. Managing this gets very complicated, very fast.

Also, we have Single Sign On. We use OneLogin, but honestly, any SAML with an API will work so keep reading.

What problem(s) are you trying to solve?

  1. Automatically inviting new users to our Github organization
  2. Automatically remove users that are no longer active
  3. Manage Github team permissions from SAML

Terraform

We terraform for every piece of infrastructure we can. Terraform has a Github provider, which we already use to manage repositories, teams, and other resources.

Managing users with terraform also makes sense since we can control everything through terraform, from the repository to user to team membership.

How does it work?

If you read through some of my previous terraform posts, you know we have a pattern where we generate terraform code using Python. We use vanilla terraform as much as possible, and Python takes care of generating that vanilla terraform.

Here’s the flow of the process:

OneLogin terraform flow

After you understand the flow, the code is really quite simple. We read from onelogin, generate a list, and then inject it into a template

@command()
@option(
	"--onelogin-client-id",
	default=lambda: os.environ.get("ONELOGIN_CLIENT_ID", ""),
)
@option(
	"--onelogin-client-secret",
	default=lambda: os.environ.get("ONELOGIN_CLIENT_SECRET", ""),
)
@option(
	"--onelogin-domain",
	default=lambda: os.environ.get("ONELOGIN_DOMAIN", "hippo.onelogin.com"),
)
def main(onelogin_client_id, onelogin_client_secret, onelogin_domain):
	config = OneLoginConfig(
		client_id=onelogin_client_id,
		client_secret=onelogin_client_secret,
		domain=onelogin_domain,
	)

	users = get_onelogin_users(config)
	github_users = get_github_users(users)

	generate_from_template("people.tf.j2", "people.tf", {"github_users": github_users})

	terraform_cmd = os.getenv("TERRAFORM_CMD", "terraform")
	system(f"{terraform_cmd} fmt -recursive .")

The template is also straightforward:

{% for github_user in github_users -%}
  {% set username = github_user.username | replace('.', '-') -%}
  {% if username|first in '1234567890' -%}
	{% set username = "_" + username -%}
  {% endif -%}
{% set team = github_user.team | replace('-', '_') -%}
data "github_user" "{{ username }}" {
  username = "{{ github_user.username }}"
}

resource "github_membership" "user_membership_{{ username }}" {
  username = data.github_user.{{ username }}.username
  role     = "member"
}

resource "github_team_membership" "{{ team }}_{{ username }}" {
  team_id  = github_team.{{ team }}.id
  username = data.github_user.{{ username }}.username
}

{% endfor -%}

Summing up

That’s pretty much it. The beauty in simplicity.

We run this every hour on our CD. IT manages OneLogin, users onboarding, offboarding, etc.

Summing up x2

I am often asked “How is your DevOps team so small?”. Decisions and processes like this is how. We remove all of the “busy work” and are religious about continuously improving and automating.