Compare commits

..

No commits in common. "main" and "v1.2.0" have entirely different histories.
main ... v1.2.0

2 changed files with 272 additions and 288 deletions

View file

@ -1,54 +1,52 @@
# Load golang image # Load golang image
FROM golang:1.21-alpine as builder FROM golang:1.21-alpine as builder
RUN apk add make RUN apk add make
ARG VERSION=undefined ARG VERSION=undefined
WORKDIR /go/src/app WORKDIR /go/src/app
# Set our build environment # Set our build environment
ENV GOCACHE=/tmp/.go-build-cache ENV GOCACHE=/tmp/.go-build-cache
# This variable communicates to the service that it's running inside # This variable communicates to the service that it's running inside
# a docker container. # a docker container.
ENV ENV_DOCKER=true ENV ENV_DOCKER=true
# Copy dockerignore files # Copy dockerignore files
COPY .dockerignore ./ COPY .dockerignore ./
# Install go deps using the cache # Install go deps using the cache
COPY go.mod go.sum ./ COPY go.mod go.sum ./
RUN --mount=type=cache,target=/tmp/.go-build-cache \ RUN --mount=type=cache,target=/tmp/.go-build-cache \
go mod download -x go mod download -x
COPY Makefile ./ COPY Makefile ./
# Copy source files # Copy source files
COPY main.go ./ COPY main.go ./
COPY cmd cmd COPY cmd cmd
COPY internal internal COPY internal internal
COPY webfingers webfingers
COPY handler handler # Build it
RUN --mount=type=cache,target=/tmp/.go-build-cache \
# Build it make build VERSION=$VERSION
RUN --mount=type=cache,target=/tmp/.go-build-cache \
make build VERSION=$VERSION # Now create a new image with just the binary
FROM gcr.io/distroless/static-debian11:nonroot
# Now create a new image with just the binary
FROM gcr.io/distroless/static-debian11:nonroot WORKDIR /app
WORKDIR /app COPY urns.yml /app/urns.yml
COPY urns.yml /app/urns.yml # Set our runtime environment
ENV ENV_DOCKER=true
# Set our runtime environment
ENV ENV_DOCKER=true COPY --from=builder /go/src/app/finger /usr/local/bin/finger
COPY --from=builder /go/src/app/finger /usr/local/bin/finger HEALTHCHECK CMD [ "finger", "healthcheck" ]
HEALTHCHECK CMD [ "finger", "healthcheck" ] EXPOSE 8080
EXPOSE 8080 ENTRYPOINT [ "finger" ]
CMD [ "serve" ]
ENTRYPOINT [ "finger" ]
CMD [ "serve" ]

454
README.md
View file

@ -1,234 +1,220 @@
# Finger # Finger
Webfinger handler / standalone server written in Go. Webfinger handler / standalone server written in Go.
## Features ## Features
- 🍰 Easy YAML configuration - 🍰 Easy YAML configuration
- 🪶 Single 8MB binary / 0% idle CPU / 4MB idle RAM - 🪶 Single 8MB binary / 0% idle CPU / 4MB idle RAM
- ⚡️ Sub millisecond responses at 10,000 request per second - ⚡️ Sub millisecond responses at 10,000 request per second
- 🐳 10MB Docker image - 🐳 10MB Docker image
## In your existing server ## In your existing server
To use Finger in your existing server, download the package as a dependency: To use Finger in your existing server, download the package as a dependency:
```bash ```bash
go get git.maronato.dev/maronato/finger@latest go get git.maronato.dev/maronato/finger@latest
``` ```
Then, use it as a regular `http.Handler`: Then, use it as a regular `http.Handler`:
```go ```go
package main package main
import ( import (
"log" "log"
"net/http" "net/http"
"git.maronato.dev/maronato/finger/handler" "git.maronato.dev/maronato/finger/handler"
"git.maronato.dev/maronato/finger/webfingers" "git.maronato.dev/maronato/finger/webfingers"
) )
func main() { func main() {
// Create the webfingers map that will be served by the handler // Create the webfingers map that will be served by the handler
fingers, err := webfingers.NewWebFingers( fingers, err := webfingers.NewWebFingers(
// Pass a map of your resources (Subject key followed by it's properties and links) // Pass a map of your resources (Subject key followed by it's properties and links)
// the syntax is the same as the fingers.yml file (see below) // the syntax is the same as the fingers.yml file (see below)
webfingers.Resources{ webfingers.Resources{
"user@example.com": { "user@example.com": {
"name": "Example User", "name": "Example User",
}, },
}, },
// Optionally, pass a map of URN aliases (see urns.yml for more) // Optionally, pass a map of URN aliases (see urns.yml for more)
// If nil is provided, no aliases will be used // If nil is provided, no aliases will be used
webfingers.URNAliases{ webfingers.URNAliases{
"name": "http://schema.org/name", "name": "http://schema.org/name",
}, },
) )
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
mux := http.NewServeMux() mux := http.NewServeMux()
// Then use the handler as a regular http.Handler // Then use the handler as a regular http.Handler
mux.Handle("/.well-known/webfinger", handler.WebfingerHandler(fingers)) mux.Handle("/.well-known/webfinger", handler.WebfingerHandler(fingers))
log.Fatal(http.ListenAndServe("localhost:8080", mux)) log.Fatal(http.ListenAndServe("localhost:8080", mux))
} }
``` ```
## As a standalone server ## As a standalone server
If you don't have a server, Finger can also serve itself. You can install it via `go install` or use the Docker image. If you don't have a server, Finger can also serve itself. You can install it via `go install` or use the Docker image.
Via `go install`: Via `go install`:
```bash ```bash
go install git.maronato.dev/maronato/finger@latest go install git.maronato.dev/maronato/finger@latest
``` ```
Via Docker: Via Docker:
```bash ```bash
docker run \ docker run --name finger /
--name finger \ -p 8080:8080 /
-p 8080:8080 \ git.maronato.dev/maronato/finger
-v ${PWD}/fingers.yml:/app/fingers.yml \ ```
git.maronato.dev/maronato/finger
``` ## Usage
## Usage If you installed it using `go install`, run
```bash
If you installed it using `go install`, run finger serve
```bash ```
finger serve To start the server on port `8080`. Your resources will be queryable via `locahost:8080/.well-known/webfinger?resource=<your-resource>`
```
To start the server on port `8080`. Your resources will be queryable via `locahost:8080/.well-known/webfinger?resource=<your-resource>` If you're using Docker, the use the same command in the install section.
If you're using Docker, the use the same command in the install section. By default, no resources will be exposed. You can create resources via a `fingers.yml` file. It should contain a collection of resources as keys and their attributes as their objects.
By default, no resources will be exposed. You can create resources via a `fingers.yml` file. It should contain a collection of resources as keys and their attributes as their objects. Some default URN aliases are provided via the built-in mapping ([`urns.yml`](./urns.yml)). You can replace that with your own or use URNs directly in the `fingers.yml` file.
Some default URN aliases are provided via the built-in mapping ([`urns.yml`](./urns.yml)). You can replace that with your own or use URNs directly in the `fingers.yml` file. Here's an example:
```yaml
Here's an example: # fingers.yml
```yaml
# fingers.yml # Resources go in the root of the file. Email address will have the acct:
# prefix added automatically.
# Resources go in the root of the file. Email address will have the acct: alice@example.com:
# prefix added automatically. # "avatar" is an alias of "http://webfinger.net/rel/avatar"
alice@example.com: # (see urns.yml for more)
# "avatar" is an alias of "http://webfinger.net/rel/avatar" avatar: "https://example.com/alice-pic"
# (see urns.yml for more)
avatar: "https://example.com/alice-pic" # If the value is a URI, it'll be exposed as a webfinger link
openid: "https://sso.example.com/"
# If the value is a URI, it'll be exposed as a webfinger link
openid: "https://sso.example.com/" # If the value of the attribute is not a URI, it will be exposed as a
# webfinger property
# If the value of the attribute is not a URI, it will be exposed as a name: "Alice Doe"
# webfinger property
name: "Alice Doe" # You can also specify URN's directly instead of the aliases
http://webfinger.net/rel/profile-page: "https://example.com/user/alice"
# You can also specify URN's directly instead of the aliases
http://webfinger.net/rel/profile-page: "https://example.com/user/alice" bob@example.com:
name: Bob Foo
bob@example.com: openid: "https://sso.example.com/"
name: Bob Foo
openid: "https://sso.example.com/" # Resources can also be URIs
https://example.com/user/charlie:
# Resources can also be URIs name: Charlie Baz
https://example.com/user/charlie: profile: https://example.com/user/charlie
name: Charlie Baz ```
profile: https://example.com/user/charlie
``` ### Example queries
<details>
### Example queries <summary><b>Query Alice</b><pre>GET http://localhost:8080/.well-known/webfinger?resource=acct:alice@example.com</pre></summary>
<details>
<summary><b>Query Alice</b><pre>GET http://localhost:8080/.well-known/webfinger?resource=acct:alice@example.com</pre></summary> ```json
{
```json "subject": "acct:alice@example.com",
{ "links": [
"subject": "acct:alice@example.com", {
"links": [ "rel": "avatar",
{ "href": "https://example.com/alice-pic"
"rel": "avatar", },
"href": "https://example.com/alice-pic" {
}, "rel": "openid",
{ "href": "https://sso.example.com/"
"rel": "openid", },
"href": "https://sso.example.com/" {
}, "rel": "http://webfinger.net/rel/profile-page",
{ "href": "https://example.com/user/alice"
"rel": "http://webfinger.net/rel/profile-page", }
"href": "https://example.com/user/alice" ],
} "properties": {
], "name": "Alice Doe"
"properties": { }
"name": "Alice Doe" }
} ```
} </details>
```
</details>
<details>
<summary><b>Query Bob</b><pre>GET http://localhost:8080/.well-known/webfinger?resource=acct:bob@example.com</pre></summary>
<details>
<summary><b>Query Bob</b><pre>GET http://localhost:8080/.well-known/webfinger?resource=acct:bob@example.com</pre></summary> ```json
{
```json "subject": "acct:bob@example.com",
{ "links": [
"subject": "acct:bob@example.com", {
"links": [ "rel": "http://openid.net/specs/connect/1.0/issuer",
{ "href": "https://sso.example.com/"
"rel": "http://openid.net/specs/connect/1.0/issuer", }
"href": "https://sso.example.com/" ],
} "properties": {
], "http://schema.org/name": "Bob Foo"
"properties": { }
"http://schema.org/name": "Bob Foo" }
} ```
} </details>
```
</details>
<details>
<summary><b>Query Charlie</b><pre>GET http://localhost:8080/.well-known/webfinger?resource=https://example.com/user/charlie</pre></summary>
<details>
<summary><b>Query Charlie</b><pre>GET http://localhost:8080/.well-known/webfinger?resource=https://example.com/user/charlie</pre></summary> ```JSON
{
```JSON "subject": "https://example.com/user/charlie",
{ "links": [
"subject": "https://example.com/user/charlie", {
"links": [ "rel": "http://webfinger.net/rel/profile-page",
{ "href": "https://example.com/user/charlie"
"rel": "http://webfinger.net/rel/profile-page", }
"href": "https://example.com/user/charlie" ],
} "properties": {
], "http://schema.org/name": "Charlie Baz"
"properties": { }
"http://schema.org/name": "Charlie Baz" }
} ```
} </details>
```
</details> ## Commands
## Commands Finger exposes two commands: `serve` and `healthcheck`. `serve` is the default command and starts the server. `healthcheck` is used by the Docker healthcheck to check if the server is up.
Finger exposes two commands: `serve` and `healthcheck`. `serve` is the default command and starts the server. `healthcheck` is used by the Docker healthcheck to check if the server is up. ## Configs
Here are the config options available. You can change them via command line flags or environment variables:
## Configs
Here are the config options available. You can change them via command line flags or environment variables: | CLI flag | Env variable | Default | Description |
| ------------------- | ---------------- | -------------------------------------- | -------------------------------------- |
| CLI flag | Env variable | Default | Description | | `-p, --port` | `WF_PORT` | `8080` | Port where the server listens to |
| ------------------- | ---------------- | -------------------------------------- | -------------------------------------- | | `-h, --host` | `WF_HOST` | `localhost` (`0.0.0.0` when in Docker) | Host where the server listens to |
| `-p, --port` | `WF_PORT` | `8080` | Port where the server listens to | | `-f, --finger-file` | `WF_FINGER_FILE` | `fingers.yml` | Path to the webfingers definition file |
| `-h, --host` | `WF_HOST` | `localhost` (`0.0.0.0` when in Docker) | Host where the server listens to | | `-u, --urn-file` | `WF_URN_FILE` | `urns.yml` | Path to the URNs alias file |
| `-f, --finger-file` | `WF_FINGER_FILE` | `fingers.yml` | Path to the webfingers definition file | | `-d, --debug` | `WF_DEBUG` | `false` | Enable debug logging |
| `-u, --urn-file` | `WF_URN_FILE` | `urns.yml` | Path to the URNs alias file |
| `-d, --debug` | `WF_DEBUG` | `false` | Enable debug logging | ## Development
### Docker config You need to have [Go](https://golang.org/) installed to build the project.
If you're using the Docker image, you can mount your `fingers.yml` file to `/app/fingers.yml` and the `urns.yml` to `/app/urns.yml`.
Clone the repo and run `make build` to build the binary. You can then run `./finger serve` to start the server.
To run the docker image with flags or a different command, specify the command followed by the flags:
```bash A few other commands are:
# Start the server on port 3030 in debug mode with a different fingers file - `make run` to run the server
docker run git.maronato.dev/maronato/finger serve --port 3030 --debug --finger-file /app/my-fingers.yml - `make test` to run the tests
- `make lint` to run the linter
# or run a healthcheck on a different finger container - `make clean` to clean the build files
docker run git.maronato.dev/maronato/finger healthcheck --host otherhost --port 3030
``` ## License
## Development This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
You need to have [Go](https://golang.org/) installed to build the project.
Clone the repo and run `make build` to build the binary. You can then run `./finger serve` to start the server.
A few other commands are:
- `make run` to run the server
- `make test` to run the tests
- `make lint` to run the linter
- `make clean` to clean the build files
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.