Skip to content

mandelsoft/ctxmgmt

Repository files navigation

A Generic and Extensible Configuration and Credential Framework for Go

This module provides some generic and extensible frameworks for

  • managing configurations by bringing together configuration providers and configuration consumers in a generic and uniform way. Any kinds of configuration settings can be combined and consistently forwarded to appropriate configuration targets. This way, although any used library may use own configuration settings, the using program can use a single uniform way to configure all those different used environments.
  • managing credentials by bringing together arbitrary credential sources, like config files or a vault credential repository and any kind of credential consumer. Hereby, programs don't nod to bother about reading various credential sources and forwarding appropriate credentials to used libraries. They just have to use the credential management and pass a generic credential context to potential credential consumer. This context then is able to provide appropriate crednetials according to the needs of the consumer.
  • data contexts responsible for a particular technical realm bundling the access API for this realm with configuration settings. Both, the configuration and the credential management are implemented ad data contexts using this framework. Hereby, the credential management context uses and incorporates a configuration context for its configuration needs

Example

Using the provided frameworks allows to bundle all required configuration in one central configuration file. It may contain one or any number of arbitrary, but typed configuration data structures in form of a YAML document. In our example this is a .appconfig file

type: generic.config.mandelsoft.de/v1
configurations:
- type: credentials.config.mandelsoft.de
  consumers:
      - identity:
          type: service.acme.corp
          hostname: localhost
        credentials:
          - type: Credentials
            properties:
              username: testuser
              password: testpassword
  repositories:
          - repository:
              type: DockerConfig/v1
              dockerConfigFile: "~/.docker/config.json"
              propagateConsumerIdentity: true

- type: application.config.acme.org
  serviceAddress: localhost:8080

The credential management offers a config object of type credentials.config.mandelsoft.de/v1. It can be used to configure supported credential repositories, here a docker config file. Arbitrary credential repositories types can be added by using libraries. Out-of-the box, HasiCorp Vault, Docker and NPM config files are supported. Additionally, explicit credential setting can be configured, here, credentials for a consumer type service.acme.corp used in out example.

A second config object (application.config.acme.org), implemented by this example, covers the configuration parameters for our demo application.

The application program now does not need to bother with config or credential handling anymore. When starting, it just uses the config file to configure the used credential context:

func RunApplication() error {

	ctx := credentials.New(ctxmgmt.MODE_SHARED)

	err := cfgutils.Configure(ctx, ".appconfig")
	if err != nil {
		return errors.Wrap(err, "reading configuration")
	}

	app, err := application.NewApplication(ctx)
	if err != nil {
		return errors.Wrap(err, "error creating application")
	}

	app.Describe()
	return nil
}

This context is then passed around, and can be used by all participants to gain access to the required information.

The application constructor uses an own configuration structure, which can be served by our application config object. Calling ApplyAllTo applies all pending configuration objects applicable for the given configuration target.

type Config struct {
	address string
}

func (a *Config) SetServiceAddress(addr string) {
	a.address = addr
}

type Application struct {
	Config
	client *service.ServiceClient
}

var _ myconfig.ConfigTarget = (*Application)(nil)

func NewApplication(ctx credentials.Context) (*Application, error) {
	app := &Application{}

	err := ctx.ConfigContext().ApplyAllTo(&app.Config)
	if err != nil {
		return nil, errors.Wrapf(err, "cannot apply to application config")
	}
	app.client, err = service.NewServiceClient(ctx, app.address)
	if err != nil {
		return nil, errors.Wrapf(err, "cannot create service client")
	}
	return app, nil
}

This way the address of the service instance to use is configured. But it does not need to care about the service client. It also gets access to the context and is responsible on its own to retrieve the required additional information from it.

It creates a specification for the required credentials, consisting of a consumption type (our service.acme.corp) and additional fields concretely describing the scenario, here, the host and port name.

func GetConsumerId(addr string) credentials.ConsumerIdentity {
	h, p, s := oci.SplitLocator(addr)
	return credentials.NewConsumerIdentity(CONSUMER_TYPE,
		hostpath.ID_HOSTNAME, h,
		hostpath.ID_PORT, p,
		hostpath.ID_PATHPREFIX, s,
	)
}

Using this specification it just asks the credential management to determine the best matching credential setting extracted from one of the configured credential sources. The credential context incorporated a configuration context, which is used to implicitly configure and update the used credential sources. The caller does not need to care about this. It just asks for credentials meeting the given constraints.

func NewServiceClient(ctx credentials.Context, addr string) (*ServiceClient, error) {
	creds, err := credentials.CredentialsForConsumer(ctx, identity.GetConsumerId(addr), identity.IdentityMatcher)
	if err != nil {
		return nil, err
	}

	s := &ServiceClient{
		address: addr,
	}

	if creds != nil {
		s.username = creds.GetProperty(credentials.ATTR_USERNAME)
		s.password = creds.GetProperty(credentials.ATTR_PASSWORD)
		s.cert = creds.GetProperty(credentials.ATTR_CERTIFICATE)
	}
	return s, nil
}

Finally, our main program just prints the configuration configured for the application and the service client.

service address: localhost:8080
service client:
 username:    testuser
 password:    testpassword
 certificate: 

The complete example can be found in examples/working-with-credentials/00-application-scenario.go.

The Tour

A tour demonstrates how to use those frameworks:

About

A Generic and Extensible Configuration and Credential Framework for Go

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages