Skip to content

kyleterry/tenyks

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tenyks

An IRC bot written in Go. Tenyks relays IRC messages to external service clients over a bidirectional gRPC stream secured with mutual TLS (mTLS). Services authenticate using client certificates that encode the channel paths they are permitted to access.

Architecture

IRC ──► tenyks (gRPC server) ──► service clients (gRPC stream, mTLS)
                ▲                        │
                └────────────────────────┘
  • tenyks — connects to IRC, fans messages out to every registered service client, and routes replies back to the appropriate channel.
  • service clients — long-lived processes that connect to tenyks, receive matched messages, and optionally send replies.
  • tenyksctl — administration CLI for issuing client certificates.

Getting started

Prerequisites

nix develop   # enter the dev shell (Go, protoc, air, etc.)

Run

go run ./cmd/tenyks

Build

go build ./cmd/tenyks ./cmd/tenyksctl

Test

go test ./...

Live reload

air

Service client certificates

Tenyks requires every service client to present a valid mTLS certificate signed by the same CA as the server. Certificates embed a custom X.509 extension that encodes the destination paths the client is allowed to access.

Use tenyksctl generate-client-certificate to issue certificates.

Basic usage (files written to disk)

tenyksctl generate-client-certificate \
  -ca-cert  ca.crt \
  -ca-key   ca.key \
  -name     weather-service \
  -paths    "libera/#weather,libera/#general" \
  -days     365

Writes weather-service.crt and weather-service.key to the current directory.

Encrypted bundle for safe delivery

When issuing a certificate for someone else, use -bundle to encrypt the certificate, private key, and CA cert into a single age-encrypted archive. The private key never needs to travel in plaintext.

Step 1 — recipient generates an age keypair (one time):

age-keygen -o key.txt
# Public key: age1abc123...

The recipient shares only the public key (age1abc123...) with you.

Step 2 — issue and encrypt the certificate:

tenyksctl generate-client-certificate \
  -ca-cert        ca.crt \
  -ca-key         ca.key \
  -name           weather-service \
  -paths          "libera/#weather" \
  -bundle \
  -age-public-key age1abc123...

Writes weather-service.tar.gz.age. Send this file to the recipient over any channel — it is safe to share publicly.

Step 3 — recipient decrypts:

age -d -i key.txt weather-service.tar.gz.age | tar xz

Produces three files:

File Description
weather-service.crt Client certificate
weather-service.key Private key (mode 0600)
ca.crt CA certificate for verifying the server

All flags

Flag Default Description
-ca-cert Path to CA certificate (required)
-ca-key Path to CA private key (required)
-name Service name; used as the certificate CN (required)
-paths (all) Comma-separated allowed destination paths
-days 365 Certificate validity period in days
-bundle false Produce an age-encrypted bundle
-age-public-key Recipient age public key (required with -bundle)
-out <name>.tar.gz.age Bundle output path (with -bundle)
-out-cert <name>.crt Certificate output path (without -bundle)
-out-key <name>.key Private key output path (without -bundle)

Path matching

Paths encode which IRC server+channel combinations a service may receive messages from. Matching rules:

Path value Matches
libera/#general Exactly that channel on that server
libera All channels on the libera server
(empty) All paths on all servers

Packages

 
 
 

Contributors

Languages