We started preparing for SOC-2 and ISO-27001 earlier this year. If you’ve been through this, you know the drill: you hire a consultant, they hand you a checklist, and you spend the next few weeks answering questions about your processes.
When I joined the organization as a Consultant in September 2025 — and got promoted to CTO in March 2026 — the security posture was… rough. Shared accounts, no enforced 2FA, passwords that hadn’t been rotated in years. The kind of setup where everyone knows the one Gmail password and nobody asks why. We spent months getting the basics right — individual accounts, MFA everywhere, secrets into a vault, infrastructure managed through code.
By the time the auditor arrived, we’d addressed most of the obvious gaps. The checklist was going well. Then we hit the access management section.
“Can you demonstrate that user access to business-critical systems is reviewed periodically?”
Sure. We had access reviews for AWS, for GitHub, for Google Workspace. All managed as code, all auditable.
“Including Jira and Confluence?”
Pause.
Jira held our project roadmaps, customer-facing delivery timelines, and internal HR boards. Confluence had architecture decision records, incident post-mortems, and financial planning documents. These weren’t side tools — they were where the actual work happened.
And nobody was managing their configuration as code. Nobody was reviewing who had access to what. Nobody had a process that would catch a stale account, an overly broad permission scheme, or a Confluence space quietly opened to “anyone with a link.”
We didn’t have a breach. We didn’t have an incident. We had something arguably worse for a compliance audit: we had no process at all.
The spreadsheet that was outdated by Monday
The first instinct was to audit manually. I opened a spreadsheet and started documenting our Jira setup. Projects, permission schemes, groups, roles, who can see what, who can do what.
Four hours in, I had 200 rows.
Atlassian’s permission model is layers upon layers. A project has a permission scheme. That scheme has grants. The grants reference groups or roles. The roles have actors. Each layer can override the one above it. The only way to see the full picture is to click through every screen in the admin panel.
I did it anyway. Three days later, I had 600+ rows documenting the state of our Jira instance as of that exact moment.
By Monday, it was already outdated. Someone had created a new project with a custom permission scheme over the weekend. The spreadsheet wasn’t a process — it was a snapshot. And snapshots don’t pass SOC-2 audits.
What I actually needed was the same thing we already had for infrastructure: a declared desired state, continuous comparison against reality, and alerts when the two diverge. I needed Terraform for Atlassian.
The search that came up empty
Terraform has providers for AWS, GCP, Azure, GitHub, Datadog, PagerDuty — basically every service with an API. Surely someone had built one for Atlassian.
I searched. Here’s what I found:
| Provider | Status | Coverage |
|---|---|---|
fourplusone/jira | ★183 — Abandoned since 2023 | Issues, comments, groups — no permission schemes, no governance |
surajrajput1024/atlassian | ★4 — Single developer, no tests | Basic permission schemes only |
atlassian/atlassian-operations | Official — Active | JSM Operations and Compass — not Jira, not Confluence |
The most mature provider was abandoned. The alternatives were incomplete. The official one was scoped to a different product entirely.
The governance layer — the part that controls who can access what, the exact thing the auditor was asking about — was a blind spot across the entire ecosystem.
Building what was missing
Building a Terraform provider from scratch is not a weekend project. But the alternative was that spreadsheet. And the spreadsheet wasn’t going to satisfy an auditor, let alone actually protect us.
I made the repository public from day one. Not out of idealism — because knowing the code is visible forces you to write proper tests and documentation from the start. Same reason some people work out at the gym instead of at home. Witnesses keep you honest.
The scope started small: three resource types to prove the full lifecycle — create, read, update, delete, import, drift detection — before scaling further. Those first three surfaced a dozen edge cases that would have compounded across a larger surface area.
The decision that mattered most for compliance: treating the relationship between projects and their permission schemes as a first-class concept. In Jira, a project doesn’t simply “have permissions.” It’s associated with a permission scheme, which contains grants, which reference groups and roles. If you want to answer “who has access to what?”, you need to traverse the entire chain. Existing providers treated projects and schemes as separate islands. I connected them.
What the Atlassian API is really like
Atlassian’s documentation and actual API behavior don’t always agree. Some discoveries along the way:
- Incomplete responses — creating a project returns only the ID and key. Nothing else from what you sent.
- Inconsistent types — the same field comes back as a number in one endpoint and a string in another. Same resource, different API versions under the hood.
- Different pagination models — Confluence and Jira use completely different approaches. Same company, same domain.
- Silent permission changes — deleting a permission scheme that’s assigned to a project makes Jira silently reassign it to the default scheme. No error, no warning. The project just loses its custom permissions.
That last one is exactly the kind of silent drift that makes compliance people nervous — and exactly what Terraform’s state comparison catches.
What it covers today
The provider reached v0.1.0 with 23 resources and 13 data sources.
| Area | What you can manage |
|---|---|
| Projects | Projects, project roles, role actors |
| Permissions | Permission schemes, grants, project-to-scheme associations |
| Issue configuration | Issue types, issue type schemes, custom fields |
| Workflows | Statuses, workflows, workflow schemes |
| Screens | Screens, tabs, fields, screen schemes, issue type screen schemes |
| Confluence | Spaces, space permissions |
Every resource supports full CRUD, import, and drift detection. Tested daily against a real Jira instance. Signed releases published to the Terraform Registry.
How we use it
A dedicated repository contains the Terraform configuration for our entire Jira and Confluence setup. A pipeline runs daily:
- Plan — compare the declared state against what’s actually configured
- Alert — if something changed outside of Terraform, the team gets notified
- Review — a human either enforces the declared state, or updates the config to acknowledge an intentional change
This gives us three things the spreadsheet never could:
Continuous monitoring — not a point-in-time audit, but a daily comparison. If a permission scheme changes at 2 AM, we see it at 8 AM.
Change history — every modification goes through a pull request. The answer to “who changed that?” is in git, not in an admin audit log nobody reads.
Reproducibility — the Jira configuration is code. New projects get the same setup, because it’s defined once and applied consistently.
The provider is open source
GitHub: github.com/lbajsarowicz/terraform-provider-atlassian
Terraform Registry: lbajsarowicz/atlassian
If any of these sound familiar, it was built for this:
You’re going through SOC-2 or ISO-27001 and need to prove that access to Jira and Confluence is managed and reviewed. Define your permission schemes, groups, and project associations as code. Git history becomes your audit trail.
Your offboarding has gaps. Manage Jira group membership through Terraform. When someone leaves, remove them from the config and apply. No checkboxes to forget.
You’re scaling and projects are inconsistent. Teams create projects with ad-hoc setups. Define standard configurations and apply them uniformly.
You want Confluence space governance. Manage who can access which spaces as code — something no other Terraform provider supports.
Getting started:
terraform {
required_providers {
atlassian = {
source = "lbajsarowicz/atlassian"
version = "~> 0.1"
}
}
}
provider "atlassian" {
url = "https://yoursite.atlassian.net"
user = "admin@yourcompany.com"
token = var.atlassian_api_token
}
Import what you already have:
terraform import atlassian_jira_project.my_project PROJ
terraform import atlassian_jira_permission_scheme.default 10001
terraform import atlassian_jira_group.developers developers
The provider tracks state, detects drift, and gives you visibility into a part of your stack that’s probably been running on trust and good intentions.
That worked fine for us too. Until the auditor asked a simple question we couldn’t answer.