4 minute read
You are a ChallMaker or only curious ? You want to understand how the chall-manager can spin up challenge instances on demand ? You are at the best place for it then.
This tutorial will be split up in three parts:
We call a “Pulumi factory” a golang code or binary that fits the chall-manager scenario API. For details on this API, refer to the SDK documentation.
The requirements are:
Create a directory and start working in it.
mkdir my-challenge
cd $_
go mod init my-challenge
First of all, you’ll configure your Pulumi factory. The example below constitutes the minimal requirements, but you can add more configuration if necessary.
Pulumi.yaml
name: my-challenge
runtime: go
description: Some description that enable others understand my challenge scenario.
Then create your entrypoint base.
main.go
package main
import (
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Scenario will go there
return nil
})
}
You will need to add github.com/pulumi/pulumi/sdk/v3/go
to your dependencies: execute go mod tidy
.
Starting from here, you can get configurations, add your resources and use various providers.
For this tutorial, we will create a challenge consuming the identity from the configuration and create an Amazon S3 Bucket. At the end, we will export the connection_info
to match the SDK API.
main.go
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/s3"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// 1. Load config
cfg := config.New(ctx, "my-challenge")
config := map[string]string{
"identity": cfg.Get("identity"),
}
// 2. Create resources
_, err := s3.NewBucketV2(ctx, "example", &s3.BucketV2Args{
Bucket: pulumi.String(config["identity"]),
Tags: pulumi.StringMap{
"Name": pulumi.String("My Challenge Bucket"),
"Identity": pulumi.String(config["identity"]),
},
})
if err != nil {
return err
}
// 3. Export outputs
// This is a mockup connection info, please provide something meaningfull and executable
ctx.Export("connection_info", pulumi.String("..."))
return nil
})
}
Don’t forget to run go mod tidy
to add the required Go modules. Additionally, make sure to configure the chall-manager
pods to get access to your AWS configuration through environment variables, and add a Provider configuration in your code if necessary.
You can compile your code to make the challenge creation/update faster, but chall-manager will automatically do it anyway to enhance performances (avoid re-downloading Go modules and Pulumi providers, and compile the scenario).
Such build could be performed through CGO_ENABLED=0 go build -o main path/to/main.go
.
Add the following configuration in your Pulumi.yaml
file to consume it, and set the binary path accordingly to the filesystem.
runtime:
name: go
options:
binary: ./main
You can test it using the Pulumi CLI with for instance the following.
pulumi stack init # answer the questions
pulumi up # preview and deploy
Now that your scenario is designed and coded accordingly to your artistic direction, you have to prepare it for an OCI registry such that Chall-Manager can process it. Make sure to remove all unnecessary files, such that the scenario is mimimal.
A scenario is an OCI blob that is delivered through an OCI registry (e.g. Docker Registry, Zot, Artifactory). To ease the creation and distribution of a scenario we will use chall-manager-cli
.
It will pack the files of a directory
inside an OCI blob of data with annotation application/vnd.ctfer-io.file
, and push it in the registry.
cd ..
oras push \
"registry.lan/my/scenario:tag" \
--artifact-type application/vnd.ctfer-io.scenario \
--media-type application/vnd.ctfer-io.file \
$(find scenario -type f)
Authentication is optional yet recommended. The same holds for certificate validation that could be turned off with --insecure
.
And you’re done. Yes, it was that easy :)
But it could be even more using the SDK !
You don’t need to archive all files.
If you prebuild the scenario, you’ll only need to pack the main
binary and Pulumi.yaml
files.
It should make chall-manager run with better in production, and reduce supply chain risks as the binary won’t be re-compiled.
A scenario can get provided additional configuration over a key=value map. Using this, you can further configure your scenario at last moment, or even reuse them.
For intance, if your challenge provide a configuration key=value pair for a Docker image to use, and your instance does too for an authorized CIDR, then you might reuse your scenario for multiple use cases.
To configure those values, please refer to the API documentation. From the SDK point of view, you can access those additional configuration key=value pairs as follows.
main.go
package main
import (
"github.com/ctfer-io/chall-manager/sdk"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
sdk.Run(func(req *sdk.Request, resp *sdk.Response, opts ...pulumi.ResourceOption) error {
// 1. Get your additional configuration pairs
image, ok := req.Config.Additional["image"]
if !ok {
return missing("image")
}
cidr, ok := req.Config.Additional["cidr"]
if !ok {
return missing("cidr")
}
// 2. Use them
// ...
// 3. Return content as always
resp.ConnectionInfo = pulumi.Sprintf("Running %s for %s", image, cidr)
return nil
})
}
func missing(key string) error {
return fmt.Errorf("missing additional configuration for %s", key)
}