Leaseweb Object Storage with Tofu
Manage your Leaseweb buckets with tofu
2025-05-11 - 3 min read
hugo@superuser:/var/log/ $ less leaseweb-s3-tofu.md
2025-05-11 - 3 min read
Leaseweb’s Object Storage is one of many European alternatives to AWS S3. However, their dashboard only lets you to manage users and groups (with policies), everything else must be done with third party tools.

Since it’s S3-compatible, we can use OpenTofu (or Terraform) with the AWS provider to manage buckets and their respective access and lifecycle policies. So let’s check it out.
To set up the AWS provider, we need to configure a few settings
So our provider configuration would look something like this:
1provider "aws" {
2 region = "nl-01"
3 access_key = var.s3_access_key
4 secret_key = var.s3_secret_key
5
6 # LSW implementation has no STS service
7 skip_credentials_validation = true
8 skip_requesting_account_id = true
9 # the AWS region is unknown to AWS hence skipping is needed.
10 skip_region_validation = true
11 endpoints {
12 s3 = "https://nl.object-storage.io"
13 }
14}
Creating buckets and their respective policies is no different than using AWS S3.
1resource "aws_s3_bucket" "this" {
2 bucket_prefix = "my-awesome-bucket-"
3}
4
5resource "aws_s3_bucket_policy" "this" {
6 bucket = aws_s3_bucket.this.id
7 policy = jsonencode({
8 Statement = [
9 {
10 Action = [
11 "s3:*"
12 ]
13 Effect = "Allow"
14 Principal = {
15 AWS = ["arn:aws:iam::00000000000000000000:user/my-user"]
16 }
17 Resource = ["arn:aws:s3:::${aws_s3_bucket.this.id}/*"]
18 }
19 ]
20 })
21}
The lifecycle policy is a bit different, though: we need to force transition_default_minimum_object_size to an empty string, otherwise the policy will fail to be created. This happens because Leaseweb doesn’t have storage classes.
1resource "aws_s3_bucket_lifecycle_configuration" "this" {
2 bucket = aws_s3_bucket.this.id
3
4 # LSW needs this in order for tofu to work
5 transition_default_minimum_object_size = ""
6 rule {
7 id = "daily"
8 filter {
9 tag {
10 key = "retention"
11 value = "daily"
12 }
13 }
14
15 expiration {
16 days = 30
17 }
18
19 status = "Enabled"
20 }
21
22}
Because Leaseweb doesn’t support STS, we are unable to fetch the account ID via Tofu. The way I got mine was by using S3 Manager.

1variable "s3_access_key" {
2 type = string
3 description = "S3 Access Key"
4 sensitive = true
5}
6
7variable "s3_secret_key" {
8 type = string
9 description = "S3 Secret Key"
10 sensitive = true
11}
12
13
14provider "aws" {
15 region = "nl-01"
16 access_key = var.s3_access_key
17 secret_key = var.s3_secret_key
18
19 # LSW implementation has no STS service
20 skip_credentials_validation = true
21 skip_requesting_account_id = true
22 # the AWS region is unknown to AWS hence skipping is needed.
23 skip_region_validation = true
24 endpoints {
25 s3 = "https://nl.object-storage.io"
26 }
27}
28
29resource "aws_s3_bucket" "this" {
30 bucket_prefix = "my-awesome-bucket-"
31}
32
33resource "aws_s3_bucket_policy" "this" {
34 bucket = aws_s3_bucket.this.id
35 policy = jsonencode({
36 Statement = [
37 {
38 Action = [
39 "s3:*"
40 ]
41 Effect = "Allow"
42 Principal = {
43 AWS = ["arn:aws:iam::00000000000000000000:user/my-user"]
44 }
45 Resource = ["arn:aws:s3:::${aws_s3_bucket.this.id}/*"]
46 }
47 ]
48 })
49}
50
51resource "aws_s3_bucket_lifecycle_configuration" "this" {
52 bucket = aws_s3_bucket.this.id
53
54 # LSW needs this in order for tofu to work
55 transition_default_minimum_object_size = ""
56 rule {
57 id = "daily"
58 filter {
59 tag {
60 key = "retention"
61 value = "daily"
62 }
63 }
64
65 expiration {
66 days = 30
67 }
68
69 status = "Enabled"
70 }
71
72}