Users
On this page, you will:
- Build the
snowflake_usermodule - Import the
SVC_TERRAFORMservice account - Create developer and admin users
User Categories
Our Snowflake setup has several categories of users:
| Category | Example | Default Role | Default Warehouse |
|---|---|---|---|
| Infrastructure | SVC_TERRAFORM |
ACCOUNTADMIN | DEVELOPER |
| Admin | JBLOGGS_ADMIN |
SYSADMIN | DEVELOPER |
| Developer | JBLOGGS |
ANALYTICS_DEVELOPER | DEVELOPER |
| Transformer | SVC_DBT (future) |
ANALYTICS_TRANSFORMER | TRANSFORMING |
| Reporter | SVC_METABASE (future) |
ANALYTICS_REPORTER | REPORTING |
| Loader | SVC_AIRBYTE (future) |
Dedicated role | LOADING |
Service accounts for tools like dbt, Metabase, and Airbyte are created when those tools are set up, not here. This page focuses on the core users needed for the data warehouse.
Users share the workload-specific warehouses we created earlier. This approach:
- Reduces cost: Fewer warehouse resumes (60-second minimum billing each)
- Simplifies management: Four warehouses instead of dozens
- Tracks costs by workload: See spend by loading, transforming, reporting, development
The User Module
The user module creates a user with role grants and a default warehouse.
mkdir -p modules/snowflake_user
main.tf
Create modules/snowflake_user/main.tf:
terraform {
required_providers {
snowflake = {
source = "Snowflake-Labs/snowflake"
version = "~> 0.99"
configuration_aliases = [snowflake.security_admin, snowflake.user_admin]
}
}
}
locals {
# Determine the default role
default_role = var.user_create_dedicated_role ? module.dedicated_role[0].role_name : upper(var.user_default_role)
# Combine default role with additional roles
all_roles = concat([local.default_role], [for r in var.user_additional_roles : upper(r)])
}
# -----------------------------------------------------------------------------
# User
# -----------------------------------------------------------------------------
resource "snowflake_user" "this" {
provider = snowflake.user_admin
name = upper(var.user_name)
login_name = upper(var.user_name)
display_name = var.user_display_name
comment = var.user_comment
email = var.user_email
first_name = var.user_first_name
last_name = var.user_last_name
default_role = local.default_role
default_warehouse = upper(var.user_default_warehouse)
# Validation for human users
lifecycle {
precondition {
condition = var.user_is_service_account || var.user_email != null
error_message = "Email is required for human users."
}
}
}
# -----------------------------------------------------------------------------
# Dedicated Role (Optional)
# -----------------------------------------------------------------------------
# Useful for data loaders that need their own role for database writes
module "dedicated_role" {
source = "../snowflake_role"
count = var.user_create_dedicated_role ? 1 : 0
providers = {
snowflake.user_admin = snowflake.user_admin
}
role_name = var.user_name
role_comment = "Dedicated role for ${var.user_name}."
}
# -----------------------------------------------------------------------------
# Role Grants
# -----------------------------------------------------------------------------
resource "snowflake_grant_account_role" "roles_to_user" {
provider = snowflake.security_admin
for_each = toset(local.all_roles)
role_name = each.value
user_name = snowflake_user.this.name
}
variables.tf
Create modules/snowflake_user/variables.tf:
variable "user_name" {
description = "The username (will be uppercased)"
type = string
}
variable "user_comment" {
description = "Description of the user"
type = string
}
variable "user_display_name" {
description = "Display name shown in the UI"
type = string
default = ""
}
variable "user_email" {
description = "Email address (required for human users)"
type = string
default = null
validation {
condition = var.user_email == null || can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", var.user_email))
error_message = "Invalid email address format."
}
}
variable "user_first_name" {
description = "First name"
type = string
default = ""
}
variable "user_last_name" {
description = "Last name"
type = string
default = ""
}
variable "user_default_role" {
description = "Default role for the user (ignored if user_create_dedicated_role is true)"
type = string
default = "PUBLIC"
}
variable "user_default_warehouse" {
description = "Default warehouse for the user"
type = string
}
variable "user_additional_roles" {
description = "Additional roles to grant to the user"
type = list(string)
default = []
}
variable "user_create_dedicated_role" {
description = "Create a dedicated role for this user (useful for data loaders)"
type = bool
default = false
}
variable "user_is_service_account" {
description = "Is this a service account? Relaxes email validation"
type = bool
default = false
}
outputs.tf
Create modules/snowflake_user/outputs.tf:
output "user_name" {
description = "The username"
value = snowflake_user.this.name
}
output "user_default_role" {
description = "The user's default role"
value = local.default_role
}
output "user_default_warehouse" {
description = "The user's default warehouse"
value = snowflake_user.this.default_warehouse
}
Import SVC_TERRAFORM
The SVC_TERRAFORM service account was created during the Getting Started section. Now we'll import it into this module-based configuration.
Create users.tf in your root Snowflake directory:
# =============================================================================
# Service Accounts
# =============================================================================
# -----------------------------------------------------------------------------
# Terraform Service Account
# -----------------------------------------------------------------------------
module "user_svc_terraform" {
source = "./modules/snowflake_user"
providers = {
snowflake.security_admin = snowflake.security_admin
snowflake.user_admin = snowflake.user_admin
}
user_name = "SVC_TERRAFORM"
user_comment = "Service account for Terraform infrastructure management."
user_display_name = "Terraform Service Account"
user_is_service_account = true
user_default_warehouse = module.warehouse_developer.warehouse_name
user_default_role = "SYSADMIN"
user_additional_roles = ["SYSADMIN", "SECURITYADMIN", "USERADMIN"]
}
Create imports.tf to import the existing user:
# =============================================================================
# Import Blocks
# =============================================================================
# These blocks import existing Snowflake resources into Terraform state.
# Remove these blocks after the initial import is complete.
import {
to = module.user_svc_terraform.snowflake_user.this
id = "SVC_TERRAFORM"
}
After Import
Once you've run terraform apply and the import is successful, delete the imports.tf file. The user is now managed by Terraform and doesn't need the import block.
Create Human Users
For human users, use a variable-driven approach. Add to variables.tf:
variable "developer_users" {
description = "Map of developer users to create"
type = map(object({
display_name = string
first_name = string
last_name = string
email = string
}))
default = {}
}
variable "admin_users" {
description = "Map of admin users (creates both _ADMIN and regular accounts)"
type = map(object({
display_name = string
first_name = string
last_name = string
email = string
}))
default = {}
}
Add to users.tf:
# =============================================================================
# Developer Users
# =============================================================================
module "user_developers" {
source = "./modules/snowflake_user"
for_each = var.developer_users
providers = {
snowflake.security_admin = snowflake.security_admin
snowflake.user_admin = snowflake.user_admin
}
user_name = each.key
user_comment = "${each.value.display_name} - Analytics Developer"
user_display_name = each.value.display_name
user_first_name = each.value.first_name
user_last_name = each.value.last_name
user_email = each.value.email
user_default_warehouse = module.warehouse_developer.warehouse_name
user_default_role = module.role_analytics_developer.role_name
}
# =============================================================================
# Admin Users
# =============================================================================
# Each admin gets two accounts:
# - USERNAME_ADMIN: For administrative tasks (SYSADMIN default)
# - USERNAME: For daily development work (ANALYTICS_DEVELOPER default)
# Admin account
module "user_admins_admin" {
source = "./modules/snowflake_user"
for_each = var.admin_users
providers = {
snowflake.security_admin = snowflake.security_admin
snowflake.user_admin = snowflake.user_admin
}
user_name = "${each.key}_ADMIN"
user_comment = "${each.value.display_name} - Admin Account"
user_display_name = "${each.value.display_name} (Admin)"
user_first_name = each.value.first_name
user_last_name = each.value.last_name
user_email = each.value.email
user_default_warehouse = module.warehouse_developer.warehouse_name
user_default_role = "SYSADMIN"
user_additional_roles = ["ACCOUNTADMIN", "USERADMIN", "SECURITYADMIN"]
}
# Developer account
module "user_admins_developer" {
source = "./modules/snowflake_user"
for_each = var.admin_users
providers = {
snowflake.security_admin = snowflake.security_admin
snowflake.user_admin = snowflake.user_admin
}
user_name = each.key
user_comment = "${each.value.display_name} - Developer Account"
user_display_name = each.value.display_name
user_first_name = each.value.first_name
user_last_name = each.value.last_name
user_email = each.value.email
user_default_warehouse = module.warehouse_developer.warehouse_name
user_default_role = module.role_analytics_developer.role_name
}
Create users.auto.tfvars:
# Non-admin developers - creates a single account
developer_users = {
# "ASMITH" = {
# display_name = "Alice Smith"
# first_name = "Alice"
# last_name = "Smith"
# email = "alice.smith@company.com"
# }
}
# Admins - creates TWO accounts: USERNAME_ADMIN and USERNAME
# Don't add admins to developer_users - they already get a developer account
admin_users = {
# "JBLOGGS" = {
# display_name = "Joe Bloggs"
# first_name = "Joe"
# last_name = "Bloggs"
# email = "joe.bloggs@company.com"
# }
}
Don't Duplicate Users
Each person should be in only ONE of these maps. Admins automatically get a developer account (JBLOGGS) alongside their admin account (JBLOGGS_ADMIN), so they don't need to be in developer_users.
Why Dual Admin Accounts?
Admins get two accounts to enforce privilege separation:
| Account | Default Role | When to Use |
|---|---|---|
JBLOGGS_ADMIN |
SYSADMIN | Creating objects, managing grants, Terraform changes |
JBLOGGS |
ANALYTICS_DEVELOPER | Daily queries, dbt development, exploring data |
This prevents accidentally running queries with elevated privileges and makes audit logs clearer about when admin access was actually needed.
Authentication
Users created by Terraform don't have passwords set. Authentication options:
- SSO/SAML (recommended for human users) - covered in SSO Setup
- Key-pair authentication (recommended for service accounts)
- Password (set manually in Snowflake UI if needed)
For service accounts, generate key pairs and store in 1Password or AWS Secrets Manager.
Commit and Deploy
Commit your changes and push to trigger the CI/CD pipeline:
git add terraform/snowflake/
git commit -m "Add snowflake_user module and import SVC_TERRAFORM"
git push
Verify in Snowflake
After the pipeline completes, verify the users:
-- Check service account exists
SHOW USERS LIKE 'SVC_%';
-- Check user configuration
DESCRIBE USER SVC_TERRAFORM;
-- Check role grants
SHOW GRANTS TO USER SVC_TERRAFORM;
Summary
You've created the user infrastructure for your data platform:
- Built the
snowflake_usermodule - Imported the
SVC_TERRAFORMservice account using an import block - Set up patterns for developer and admin users with shared warehouses
Service Accounts for Tools
Service accounts for dbt, Metabase, Airbyte, and other tools are created when those tools are set up. This keeps the configuration focused and avoids creating unused accounts.
What's Next
With users in place, you can organise data within databases using schemas. In the next section, you'll create the schema module and set up the standard schema structure.
Continue to Schemas →