Leaseweb Object Storage with Tofu

Guide on managing Leaseweb Object Storage with OpenTofu for S3-compatible buckets, including setup and lifecycle policies.

Leasewebs Object Storage is one of many AWS S3 European alternatives. But, their dashboard only allows you to manage users and groups (with policies), everything else must be done on third party tools.

Leaseweb Object Storage Dashboard

Being S3 compatible, we can use OpenTofu (or Terraform) with the AWS provider to manage buckets and their respective access and lifecycle policies. Let’s check it then.

Provider

To setup the AWS provider, we need to set some configurations

  • Set the region1
  • Disable STS1
  • Force S3 URL
  • Skip region validation

So our provider configuration would be 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}

Resources

The bucket and respective policy creation aren’t any 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, we need to force transition_default_minimum_object_size to a empty string, otherwise it will fail to create the policy. 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}

ARN

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.

S3 Manager object listing

Full example

 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}