Categorygithub.com/containerssh/configuration/v2
modulepackage
2.1.0
Repository: https://github.com/containerssh/configuration.git
Documentation: pkg.go.dev

# README

ContainerSSH - Launch Containers on Demand

ContainerSSH Configuration Library

Go Report Card LGTM Alerts

This library provides configuration client and server components for dynamic SSH configuration.

⚠⚠⚠ Warning: This is a developer documentation. ⚠⚠⚠
The user documentation for ContainerSSH is located at containerssh.io.

Creating a configuration server

The main use case of this library will be creating a configuration server to match the current release of ContainerSSH in Go.

First, you need to fetch this library as a dependency using go modules:

go get github.com/containerssh/configuration

Next, you will have to write an implementation for the following interface:

type ConfigRequestHandler interface {
	OnConfig(request configuration.ConfigRequest) (configuration.AppConfig, error)
}

The best way to do this is creating a struct and adding a method with a receiver:

type myConfigReqHandler struct {
}

func (m *myConfigReqHandler) OnConfig(
    request configuration.ConfigRequest,
) (config configuration.AppConfig, err error) {
    // We recommend using an IDE to discover the possible options here.
    if request.Username == "foo" {
        config.DockerRun.Config.ContainerConfig.Image = "yourcompany/yourimage"
    }
    return config, err
}

Warning! Your OnConfig method should only return an error if it can genuinely not serve the request. This should not be used as a means to reject users. This should be done using the authentication server. If you return an error ContainerSSH will retry the request several times in an attempt to work around network failures.

Once you have your handler implemented you must decide which method you want to use for integration.

The full server method

This method is useful if you don't want to run anything else on the webserver, only the config endpoint. You can create a new server like this:

srv, err := configuration.NewServer(
	http.ServerConfiguration{
        Listen: "0.0.0.0:8080",
    },
	&myConfigReqHandler{},
	logger,
)

The logger parameter is a logger from the ContainerSSH log library.

Once you have the server you can start it using the service library:

lifecycle := service.NewLifecycle(srv)
err := lifecycle.Run()

This will run your server in an endless fashion. However, for a well-behaved server you should also implement signal handling:

srv, err := configuration.NewServer(
    http.ServerConfiguration{
        Listen: "0.0.0.0:8080",
    },
    &myConfigReqHandler{},
    logger,
)
if err != nil {
    // Handle error
}

lifecycle := service.NewLifecycle(srv)

go func() {
    //Ignore error, handled later.
    _ = lifecycle.Run()
}()

signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
go func() {
    if _, ok := <-signals; ok {
        // ok means the channel wasn't closed, let's trigger a shutdown.
        lifecycle.Shutdown(
            context.WithTimeout(
                context.Background(),
                20 * time.Second,
            )
        )
    }
}()
// Wait for the service to terminate.
lastError := lifecycle.Wait()
// We are already shutting down, ignore further signals
signal.Ignore(syscall.SIGINT, syscall.SIGTERM)
// close signals channel so the signal handler gets terminated
close(signals)

if err != nil {
    // Exit with a non-zero signal
    fmt.Fprintf(
        os.Stderr,
        "an error happened while running the server (%v)",
        err,
    )
    os.Exit(1)
}
os.Exit(0)

Note: We recommend securing client-server communication with certificates. The details about securing your HTTP requests are documented in the HTTP library.

Integrating with an existing HTTP server

Use this method if you want to integrate your handler with an existing Go HTTP server. This is rather simple:

handler, err := configuration.NewHandler(&myConfigReqHandler{}, logger)

You can now use the handler variable as a handler for the http package or a MUX like gorilla/mux.

Using the config client

This library also contains the components to call the configuration server in a simplified fashion. To create a client simply call the following method:

client, err := configuration.NewClient(
	configuration.ClientConfig{
        http.ClientConfiguration{
            URL: "http://your-server/config-endpoint/"
        }
    },
	logger,
    metricsCollector,
)

The logger is a logger from the log library, the metricsCollector is supplied by the metrics library.

You can now use the client variable to fetch the configuration specific to a connecting client:

connectionID := "0123456789ABCDEF"
appConfig, err := client.Get(
    ctx,
    "my-name-is-trinity",
    net.TCPAddr{
        IP: net.ParseIP("127.0.0.1"),
        Port: 2222,
    },
    connectionID,
) (AppConfig, error)

Now you have the client-specific configuration in appConfig.

Note: We recommend securing client-server communication with certificates. The details about securing your HTTP requests are documented in the HTTP library.

Loading the configuration from a file

This library also provides simplified methods for reading the configuration from an io.Reader and writing it to an io.Writer.

file, err := os.Open("file.yaml")
// ...
loader, err := configuration.NewReaderLoader(
	file,
    logger,
    configuration.FormatYAML,
)
// Read global config
appConfig := &configuration.AppConfig{}
err := loader.Load(ctx, appConfig)
// Read connection-specific config:
err := loader.LoadConnection(
    ctx,
    "my-name-is-trinity",
    net.TCPAddr{
        IP: net.ParseIP("127.0.0.1"),
        Port: 2222,
    },
    connectionID,
    appConfig,
)

As you can see these loaders are designed to be chained together. For example, you could add a HTTP loader after the file loader:

httpLoader, err := configuration.NewHTTPLoader(clientConfig, logger)

This HTTP loader calls the HTTP client described above.

Conversely, you can write the configuration to a YAML format:

saver, err := configuration.NewWriterSaver(
    os.Stdout,
    logger,
    configuration.FormatYAML,
)
err := saver.Save(appConfig)

# Functions

NewClient creates a new configuration client that can be used to fetch a user-specific configuration.
NewHandler creates a HTTP handler that forwards calls to the provided h config request handler.
NewHTTPLoader loads configuration from HTTP servers for specific connections.goland:noinspection GoUnusedExportedFunction.
NewReaderLoader loads YAML files from reader.
NewServer returns a complete HTTP server that responds to the configuration requests.goland:noinspection GoUnusedExportedFunction.
NewWriterSaver creates a config saver that writes the data in YAML format to the specified writer.

# Constants

ContainerSSH has received an invalid response from the configuration server or the network connection broke.
ContainerSSH has received a non-200 response code when calling a per-user backend configuration from the configuration server.
FormatJSON reads/writes in JSON format.
FormatYAML reads/writes in YAML format.
The ContainerSSH configuration server is now available at the specified address.
ContainerSSH is sending a quest to the configuration server to obtain a per-user backend configuration.
ContainerSSH has received a per-user backend configuration from the configuration server.
MetricNameConfigBackendFailure is the number of request failures to the configuration backend.
MetricNameConfigBackendRequests is the number of requests to the config server.
The Listen option in the root configuration is deprecated since ContainerSSH 0.4.

# Structs

AppConfig is the root configuration object of ContainerSSH.goland:noinspection GoDeprecation.
noinspection GoNameStartsWithPackageName.
ConfigRequest is the request object passed from the client to the config server.
ConfigResponse is the entire response from the config server swagger:response ConfigResponse.
ConfigResponseBody is the structure representing the JSON HTTP response.

# Interfaces

Client is the interface to fetch a user-specific configuration.
ConfigRequestHandler is a generic interface for simplified configuration request handling.
ConfigSaver is a utility to store configuration.
Loader is a utility to load and update an existing configuration structure.

# Type aliases

Format describes the format of the file being read.