## 1. Project Overview & Quickstart (patterns/changelog-drafter)

# changelog-drafter

Open-source repository patterns/changelog-drafter

### Repository Details
- **Repository:** [patterns/changelog-drafter](https://github.com/patterns/changelog-drafter)
- **Primary Language:** Code

*Note: High-volume repository documentation is actively indexed and synchronized by YakaAI.*

## 2. Official Technical Reference & Guides (patterns/patterns.github.io)

## File: _config.yml

plugins:
  - jekyll-relative-links
relative_links:
  enabled: true
  collections: true
include:
  - CONTRIBUTING.md
  - README.md
  - LICENSE.md
  - COPYING.md
  - CODE_OF_CONDUCT.md
  - CONTRIBUTING.md
  - ISSUE_TEMPLATE.md
  - PULL_REQUEST_TEMPLATE.md

theme: jekyll-theme-tactile

---

## File: about.md

# About
These journal entries are to help my future self remember odds and ends.

## § DIY
Gemini by design encourages the client implementation to be done in ~100 LOC. It is asking folks to build it. We are in a time of starting over, "going back to school", and that's alright.

=====

## * Notes, Lessons, Monologue
* Source the guides, "Here is the short list of articles that help configure X:"
* Mainly as a journal entry, try to avoid repeating information, but explain notes from the guides where there was something specific required.

_________________________________________
2022 興怡 | Always wrong, sometimes lucky

---

## File: iihk.md

# SSH InsecureIgnoreHostKey

[=> SSH Host Key](https://skarlso.github.io/2019/02/17/go-ssh-with-host-key-verification/)
[=> TLS Steps](https://gemini.circumlunar.space/docs/tls-tutorial.gmi)
[=> SSH](https://www.digitalocean.com/community/tutorials/understanding-the-ssh-encryption-and-connection-process)

## §1 /TOFU
Among our tasks was how to mimic the way SSH negotiates the first connection to a remote. The user continue prompt was already a familiar interaction. Important because our dialer must keep the `known_hosts` file to track "trusted" capsules. The other subtle point is the reason behind this act of trust -- the self-signed certificate. Meaning, capsules on self-signed certificates are assumed to be a fast path to launching Gemini content, and common place. To sum up:

* keep `known_hosts` file 
* prompt user to confirm first visit
* identify self-signed certificates

## §2 /Known_capsules
To begin the recovery flow, we use the prompt bit for self-signed certificates. The other recovery bit flags are `gmi.AcceptUAE` and `gmi.UAEReject` which mean continue and halt, respectively:

```
func knownCapsules(ctx context.Context, capsule string, cert *x509.Certificate, isv Mask) bool {
	if !isv.Has(PromptUAE) {
		return false
	}
```

We made this bitmask configurable (via the `-json` commandline argument), and assigned in the safe defaults as prompt (`gmi.PromptUAE`). 

Then the "trick" is to invoke `HostKeyCallback`: 

```
import kh "golang.org/x/crypto/ssh/knownhosts"
...
func searchKnown(cert *x509.Certificate, capsule string, kcp string) error {
	sshpk, err := ssh.NewPublicKey(cert.PublicKey)
	if err != nil {
		log.Printf("DEBUG crt to ssh key failed, %v", err)
		return err
	}
	abs, err := filepath.Abs(kcp)
	if err != nil {
		log.Printf("DEBUG known_capsules path, %v", err)
		return err
	}
	hostKeyCallback, err := kh.New(abs)
	if err != nil {
		log.Printf("DEBUG callback not created, %v", err)
		return err
	}
	addr, err := net.ResolveTCPAddr("tcp", capsule)
	if err != nil {
		log.Printf("DEBUG resolve, %v", err)
		return err
	}
	err = hostKeyCallback(capsule, addr, sshpk)
	if err != nil {
		log.Printf("DEBUG known error, %v", err)
	}
```

The `ssh/knownhosts` package has the `New()` function which creates a callback for us. We just need to supply the absolute path to our `known_capsules` file.

Also, we use the filename `known_capsules` to emphasize that the remotes are capsules that are tracked, instead of SSH hosts.

The callback takes capsule name, IP address, and public key as parameters. To find the public key, we obtain the public key of the certificate, then use `ssh.NewPublicKey`.

## §3 /User Prompt
For the time being, we have a placeholder. The log output shows the step/event where it is appropriate to make the UI to prompt the user. Currently, it acts as if the choice is `Y` and continues. It's worth pointing out because we have a todo, and it maps out the continue flow. For continue flow, the new remote needs a) its public key produced, and b) to be appended to the `known_hosts` file:

```
	sshpk, err := ssh.NewPublicKey(cert.PublicKey)
	if err != nil {
		return fmt.Errorf("Capsule prompt failed new key, %w", err)
	}
	line := kh.Line([]string{capsule}, sshpk)
	file, err := os.OpenFile(abs, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 644)
	if err != nil {
		return fmt.Errorf("Capsule prompt failed file, %w", err)
	}
	defer file.Close()
	_, err = file.WriteString(line)
	if err != nil {
		return fmt.Errorf("Capsule prompt failed append, %w", err)
	}
	file.WriteString("\n")
```


## §4 /Self-signed Cert
Sounds weird, but the TLS dial has to fail THEN we have an error (more in notes below). This error can be cast into different types. `x509.UnknownAuthorityError` occurs for self-signed certificates because the CA is not recognized.

Snippet of code that identifies self-signed certificates:

```
func certFrom(err error) *x509.Certificate {
	switch et := err.(type) {
	case x509.UnknownAuthorityError:
		uae, _ := err.(x509.UnknownAuthorityError)
		return uae.Cert
```


---

## * Notes, Lessons, Monologue
* When? During prototype/poc, when iterating inside controlled networks; when you want to concentrate on the functional requirements. The part I really like is the naming clearly identifies `InsecureIgnoreHostKey` as dangerous. So any code review should easily flag it when found in commits.

* Self-signed certs? It can be argued whether self-signed certs are too much risk. May be historical, and as time progresses it will change. *Let's Encrypt* definitely changed things for the better. I was able to obtain a cert with their CA for __free__. You need your own domain name, and be able to configure httpd to respond to the ACME requests. So in theory, all web servers can say goodbye to self-signed certs.

* Why waste TLS dial for the error type? I know it seems wasteful. Consider this. The best flow is the standard TLS dial with all the checks in verify. So making the initial TLS dial is actually our ideal, and the recovery flow is to accomodate (Gemini) self-signed certs.

###### 2022 興怡 | Always wrong, sometimes lucky

---

## File: index.md

# Developer Journal

## 05/2022 SSH InsecureIgnoreHostKey
[=> HostKeyCallback & InsecureIgnoreHostKey in ssh/knownhosts](iihk.html)

## 05/2022 TLS InsecureSkipVerify
[=> VerifyConnection & InsecureSkipVerify in tls.Config](isv.html)

## 02/2022 OpenBSD Gemini Vger
[=> OpenBSD Gemini Vger Configuration](vger.html)

## 02/2022 OpenBSD PF Router 
[=> OpenBSD PF Router Build](pf.html)


---

[=> About](about.html)
[=> WWW archive](https://www.tfx.tw/2020/)

###### 2022 興怡 | Always wrong, sometimes lucky

---

## File: isv.md

# TLS InsecureSkipVerify

[=> VerifyConnection](https://pkg.go.dev/crypto/tls@master#example-Config-VerifyConnection)
[=> TLS Steps](https://gemini.circumlunar.space/docs/tls-tutorial.gmi)
[=> Solderpunk Demo3](https://tildegit.org/solderpunk/gemini-demo-3)
[=> SSL/TLS](https://security.stackexchange.com/q/20803/5568)

## §1 /Certificates
*Let's Encrypt* is a tremendous life saver. It eliminated the mystery and wall around certificates. A TLS connection to our capsule becomes:

```
tls.Dial("tcp", url.Host, nil)
```

That's it. Seems __too__ easy. If every server used *Let's Encrypt*, we would be done. In reality, we only found two other capsules that run on CA certificates. Enter the `InsecureSkipVerify` toggle. To define alternate verify logic, we enable the toggle with `tls.Config`, and supply our version in the `VerifyConnection` hook.

## §2 /VerifyConnection
The Go doc example is the critical part:

```
	// VerifyConnection can be used to replace and customize connection
	// verification. This example shows a VerifyConnection implementation that
	// will be approximately equivalent to what crypto/tls does normally to
	// verify the peer's certificate.

	// Client side configuration.
	_ = &tls.Config{
		// Set InsecureSkipVerify to skip the default validation we are
		// replacing. This will not disable VerifyConnection.
		InsecureSkipVerify: true,
		VerifyConnection: func(cs tls.ConnectionState) error {
			opts := x509.VerifyOptions{
				DNSName:       cs.ServerName,
				Intermediates: x509.NewCertPool(),
			}
			for _, cert := range cs.PeerCertificates[1:] {
				opts.Intermediates.AddCert(cert)
			}
			_, err := cs.PeerCertificates[0].Verify(opts)
			return err
		},
	}
```

Running it against CA certs continued to produce TLS connections successfully. At the same time, it correctly rejected the non-CA capsules. Which meant that `InsecureSkipVerify` can be enabled (`true`), without automatically opening the floodgates to every connect request. Now we can start, and check for properties on the remote cert.

## §3 /UnknownAuthorityError
Self-signed certs will cause the standard `tls.Dial` to error with `x509.UnknownAuthorityError`. We test for the error type and cast it to utilize the cert:

```
func certFrom(err error) *x509.Certificate {
	switch et := err.(type) {
	case x509.UnknownAuthorityError:
		uae, _ := err.(x509.UnknownAuthorityError)
		return uae.Cert
```

Then within our alternate verify, we take the cert and promote it to a CA root cert in the `x509.VerifyOptions`. Notice that we use a guard to condition this logic. It is permitted when we configure with the `gmi.AcceptUAE` bit. It is also permitted when we configure with the `gmi.PromptUAE` bit, but the cert must belong to a capsule from the `known_hosts` file (more in next write-up).

```
	if isv.Has(AcceptUAE) || known && isv.Has(PromptUAE) {
		// treat self-signed cert as if root
		cert.IsCA = true
		opts.Roots = x509.NewCertPool()
		opts.Roots.AddCert(cert)
	}
```

## §4 /HostnameError
From Go 1.15 release, the Subject Alternate Name (SAN) is expected and Common Name (CN) becomes legacy. So there can be capsules on CN until their expiration/renewal generates replacement certs. The legacy CN is exposed as `x509.HostnameError`:

```
	case x509.HostnameError:
		hne, _ := err.(x509.HostnameError)
		if hne.Certificate.Subject.CommonName == hne.Host {
			return hne.Certificate
		}
```

Since it is possible for different name related issues to fall in this category, we use the `Host` value that caused the error, and compare it to the `CommonName` in the cert. This will tell us that the CN was the source of the error. By falling through for other errors, the TLS dial will stop the connection (and can be used for further analysis).

Then back in our alternate verify, we take the cert to obtain its CN and include it with the `DNSNames`; the list for SAN.

```
	if isv.Has(AcceptLCN) {
		// inject SAN
		leaf.DNSNames = append(leaf.DNSNames, cert.Subject.CommonName)
	}
```

Notice the guard statement prevents the legacy CN recovery unless the `gmi.AcceptLCN` bit is enabled by configuration. So the condition for `gmi.PromptLCN` bit is conspicuously absent. We just haven't built that yet; in terms of severity, the CN is checked, not bypassed technically so we bought more time to work on it.

## §5 /CertificateInvalidError
The expired cert is the remaining case that were encountered among the capsules toured. Since an expired cert is not listed as an exception to rules (Gemini/ otherwise), we left this case for last. For now only reserved configuration to consider for recovery, but feel there is no rush. To detect the case, the error type is `x509.CertificateInvalidError`:

```
	case x509.CertificateInvalidError:
		cie, _ := err.(x509.CertificateInvalidError)
		if cie.Reason == x509.Expired {
			log.Printf("DEBUG Expired cert, %s", cie.Detail)
			return cie.Cert
		}
```

Since we don't provide any recovery currently, it will always cause the TLS connection to stop.

---

## * Notes, Lessons, Monologue
* Bitmasks? With masks, we take the approach to combine recovery options and apply them on the recovery `tls.Dial`. It wasn't our initial attempt which was to loop the `tls.Dial`, and only toggle one recovery option each pass; the idea being to only enable the least number of options. In concept it sounds good, but the code was garbage. Maybe we can think of a design that is elegant, but for now we're doing masks.
* `VerifyPeerCertificate`? We initially experimented with implementing this hook and failed repeatedly. We just don't have enough understanding yet.
* Opinion? I'm a big fan of the toggle `InsecureSkipVerify`. Let me explain! NOT the use, but more about its design. It recognizes that there is real need to run that flow, whether a team is doing testing or there is a local/controlled network. Making this toggle built-in, accelerates that necessary work, while at the same time makes it explicit that it must be temporary or isolated, and reviewed under a microscope before shipping. I think this kind of language decision is what was meant when Go was charged with reducing stagnation and fear of touching legacy code. 

###### 2022 興怡 | Always wrong, sometimes lucky

---

## File: pf.md

# OpenBSD PF Router

[=> OpenBSD PF Router Build](https://www.openbsd.org/faq/pf/example1.html)
[=> Home Router Build](https://hackaday.com/2018/04/12/building-the-perfect-home-router/)
[=> Ars Linux Router Build](https://arstechnica.com/gadgets/2016/04/the-ars-guide-to-building-a-linux-router-from-scratch/)
[=> Router7 Build](https://michael.stapelberg.ch/posts/2021-07-10-linux-25gbit-internet-router-pc-build/)

## §1 /Networking
Used the guide exactly to set hostname.em0 for autoconf. Partly to make sure the uplink would work. Ultimately, like the Router7 article, the lease would change rarely allowing it to be safe to use a pseudo-static address config.

This is also related to the DNS record that we expose for the public gemini hostname. Ideally, we want to do DynDNS setup, but at this time it's skipped.

## §2 /Firewall
Followed exactly as shown in the guide. The line to pass traffic to the web server is adjusted for the Let's Encrypt configuration. Afterwards, we omit it and have a similar rule for port 1965 to allow gemini requests.

To check the configuration before applying:
```
# pfctl -n -f /etc/pf.conf
```

## §3 /DNS
Follow the guide. The one addition we make is inclusion of Oznu's NXDOMAIN list to sinkhole ads.

```
#### file: /var/unbound/etc/unbound.conf
        include: /var/unbound/etc/unbound.bl
forward-zone:
        name: "."
        forward-addr: 1.1.1.1
```

## §4 /Equipment
We had a regular D-Link ethernet hub that was good to go from the OpenBSD box and expand out to 8 more ports. This saved us from needing an expansion card or purchasing a hub brand new.

Not as power efficient as the SOC options. So when summer rolls around and temps rise, it may be important to investigate the NanoPi again.

=====

## * Notes, Lessons, Monologue
* Why? Our EdgeRouter Lite stopped working. Similar to one of the articles, we thought about buying a replacement and for some period we swapped in the wifi router in dual role of uplink and wifi access. After reading and searching, very close to ordering the NanoPi R4S. Then saw our under utilized old NAS server which ran SmartOS from Joyent. Also a build from Ars guides.
* OpenBSD? This was our favorite OS in the distant past (95/96?) and we revisited before until it wasn't convenient to run your own SMTP/DNS/HTTPD server. It still seems as good as ever, if not better (has ARM support in the field).
* Unbound? We have a todo to revisit and try its native versus the DNSCrypt Proxy's upstream TLS connections.

_________________________________________
2022 興怡 | Always wrong, sometimes lucky

---

## File: vger.md

# Gemini, the Middle Child Between Mercury and Apollo
Here is the short list of articles that helped us configure:

[=> Gemini server configuration on OpenBSD](https://0x19.org/posts/2022-01-02.php)
[=> Vger Gemini server](https://tildegit.org/solene/vger/)
[=> OpenBSD and Let's Encrypt](https://www.openbsdhandbook.com/services/webserver/ssl/)


## § TLS
Just follow the steps for the acme-client. Don't install Certbot. It has dependencies and adds endless packages required as a Python application. (Probably a good candidate for being Snap'd or Dockerized)

We use the FQDN (gemini.tfx.tw) when the examples show the IP address or the root domain name. There are trade offs; since we do not have a static IP we will have to deal with updating DNS records as necessary.

For Cloudflare DNS, we want to stop the proxy option on the A and CNAME records because it expects HTTPS.

We may need to add the cron job to renew the certificate in 90 days. Skipped it for now.

=====

## * Notes, Lessons, Monologue
* Is Gemini good? The protocol has many critics and the FAQ addresses the most pressing concerns. There it's stated that the purpose is not to replace web pages, but rather to fill the space between Mercury/Gopher and Apollo/Web. Reading these limitations, it reminded me of the early days of Twitter when messages were only 128 characters, and adopters tried to explain its immediacy.
* Why abandon CSS styling? The FAQ answer promotes the responsiblity of presentation to the client. It states matter of factly that each user has their own preference, and basically how ridiculous the demand can be on the implementation to achieve a consistent user experience. To some, this may seem like a relinquishing of the problem. However in my mind, this is great in its admission of real world stresses. One of the claims that surface weekly now that makes me chuckle is that the web browser promise was to develop once and have your app run across platforms. How naive we were. 

_________________________________________
2022 興怡 | Always wrong, sometimes lucky