# My Notes

Enjoy your stay here!


# Getting Started

## Common tools

### Source Code Management

#### Github

{% embed url="<https://github.com>" %}

* Your no-reply email can be found at <https://github.com/settings/emails> and it looks like `${USER_ID}+${USERNAME}@users.noreply.github.com`

#### Gitlab

{% embed url="<https://gitlab.com>" %}

* Your no-reply email can be found at <https://gitlab.com/-/user_settings/profile> and it looks like `${USER_ID}-${USERNAME}@users.noreply.gitlab.com`

## Creating your keys

Keys are used for platforms to identify you. It's recommended to use Public-Key-Infrastructure (PKI) technologies for your authentication keys, and the below covers generating and submission of SSH and GPG keys. Remember to never send anyone your private keys.

### GPG Keys

GPG keys are used to sign commits. To generate a GPG key, run:

```bash
gpg --full-gen-key
```

Enter in options as shown in your terminal:

1. RSA and RSA
2. 4096-bits long
3. 1 year validity
4. Enter any name for the Real name field (doesn't have to be your real name)
5. Use the no-reply email of the platform you are intending to use the GPG key with for the Email field (this is important for personal data hygiene)
6. Use a computer identifier for the Comment field (ideally you can recognise which device it comes from based on this field)

List your GPG keys using:

```bash
gpg --list-secret-keys --keyid-format=LONG
```

Identify the key to export. The ID of the key can be found in the line `rsa4096/${KEY_ID}`

Export your GPG key using:

```bash
gpg --armor --export ${KEY_ID};
```

Your exported GPG public key should look like:

```bash
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQINB...
...
...
-----END PGP PUBLIC KEY BLOCK-----
```

Copy that block of text and paste it into wherever it needs to be.

### SSH Keys

SSH keys are used to authenticate you when you are cloning repositories or pushing code changes. To generate an SSH key, run:

```bash
ssh-keygen -t rsa -b 8192
```

Save it to `~/.ssh/id_rsa` if it's your primary SSH key.

To get your public key, assuming it's your primary SSH key, run:

```bash
cat ~/.ssh/id_rsa.pub
```

Your public key should look like:

```
ssh-rsa AAAA..... ${USERNAME}@${NETWORK_HOSTNAME}
```

Copy that block of text and paste it into wherever it needs to be

Remember never to share your private key at `~/.ssh/id_rsa` with anyone.

## Uploading your keys

In order for your service provider to identify you, you will have to submit your **public keys** to the service provider via their web UI. Your computer will sign messages using your private keys before sending them to the service provider who will then verify that the messages can be validated against your pre-uploaded public keys.

### Github

Your SSH and GPG keys will be available at:

{% embed url="<https://github.com/settings/keys>" %}

Navigate to that page and click on **New SSH key**, give it a name which can identify your machine and paste in your SSH public key from above

Scroll down and click on **New GPG key**, give it a name which can identify your machine and paste in your public GPG key from above.

### GItlab

Your SSH keys will be available at:

{% embed url="<https://gitlab.com/-/user_settings/ssh_keys>" %}

Navigate to that page and click on **Add new key**, give it a name which can identify your machine and paste in your SSH public key from above

Your GPG keys will be available at:

{% embed url="<https://gitlab.com/-/user_settings/gpg_keys>" %}

Navigate to that page and click on **Add new key**, give it a name which can identify your machine and paste in your GPG public key from above

## Configuring GPG key usage

Finally, we configure our machine to use the keys we generated. SSH keys do not usually face any issues and the SSH agent will be able to find the correct keys to use from `~/.ssh`. We will cover GPG key configurations.

### Configuring a standalone local repository

The configuration can be found at `.git/config` relative to your project's root directory. Open it up and add the following:

```
[user]
  name = your name
  email = yourpublicemail@domain.com
  signingkey = ${KEY_ID}
```

Get the key ID by running `gpg --list-secret-keys --keyid-format=LONG` and getting the key ID from the line indicating `rsa4096/${KEY_ID}`

### Configuring a directory and all subpaths

The following assumes that you have a directory named `github.com` where all your Github projects are stored. Open the root Git configuration file at `~/.gitconfig` and add in:

```bash
# for all github commits...
[includeIf "gitdir:**/github.com/"]
  path = ~/.github.com.gitconfig
```

Open the conditionally included file at `~/.github.com.gitconfig` and add in:

```
[user]
  name = your name
  email = yourpublicemail@domain.com
  signingkey = ${KEY_ID}
```

Similar to above, get the key ID by running `gpg --list-secret-keys --keyid-format=LONG` and getting the key ID from the line indicating `rsa4096/${KEY_ID}`


# VSCodium

## Extensions not loading in VSCodium

1. Find your binary location: `which codium`
2. Navigate to that directory and search for a file called `product.json` which should be in `./resources/app/` relative to the root of the application directory (the binary should be in `./bin`)
3. Search for `"extensionsGallery"` and replace the URLs as follows:

```
"extensionsGallery": {
    "serviceUrl": "https://marketplace.visualstudio.com/_apis/public/gallery",
    "cacheUrl": "https://vscode.blob.core.windows.net/gallery/index",
    "itemUrl": "https://marketplace.visualstudio.com/items"
}
```

## 429 Too Many Requests on Ubuntu/Debian-based systems in VSCodium

Add the GPG key:

```
wget -qO - https://gitlab.com/paulcarroty/vscodium-deb-rpm-repo/raw/master/pub.gpg | gpg --dearmor | sudo dd of=/etc/apt/trusted.gpg.d/vscodium-archive-keyring.gpg;
```

Change `/etc/apt/sources.list.d/vscodium.list` to:

```
deb [signed-by=/etc/apt/trusted.gpg.d/vscodium-archive-keyring.gpg] https://paulcarroty.gitlab.io/vscodium-deb-rpm-repo/debs/ vscodium main
```

Update and upgrade:

```
sudo apt update;
sudo apt upgrade;
```


# Go

## Cheatsheet/References

The following have now been implemented in a GitHub repository that can be found at:

{% embed url="<https://github.com/zephinzer/template-go-service>" %}

### Dockerfile

Pair the following with the Makefile below

```
ARG GO_VERSION=1.21
FROM golang:${GO_VERSION}-alpine AS build
RUN apk add --no-cache ca-certificates git g++ make
WORKDIR /go/src/app
COPY ./go.mod ./go.sum ./
RUN go mod download -x
COPY ./*.go ./
COPY ./cmd ./cmd
COPY ./internal ./internal
ENV CGO_ENABLED=0
RUN make install-swaggo-ci
RUN make docs-swaggo
RUN make deps
RUN make binary
RUN sha256sum ./bin/app > ./bin/app.sha256

FROM scratch AS final
COPY --from=build /go/src/app/bin/app /app
COPY --from=build /go/src/app/bin/app.sha256 /app.sha256
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
ENTRYPOINT ["/app"]
```

### Makefile

This Makefile contains generic-use recipes for a standard Go project

```
APP_NAME := app
BIN_PATH := ./bin
CMD_PATH := ./cmd
IMAGE_PATH := ${APP_NAME}/${APP_NAME}
IMAGE_REGISTRY := docker.io
IMAGE_TAG := latest

# everything before this next line is configurable in the Makefile.properties
-include Makefile.properties
# everything after this line is a derived value from the above variables
IMAGE_URL := ${IMAGE_REGISTRY}/${IMAGE_PATH}

ifeq ("${GOOS}", "windows")
BINARY_EXT := ".exe"
endif

binary:
	@echo "building binary for ${APP_NAME} for os/arch $$(go env GOOS)/$$(go env GOARCH)..."
	@mkdir -p "${BIN_PATH}"
	@go build \
		-ldflags "\
			-extldflags 'static' -s -w \
			-X ${APP_NAME}/internal/constants.AppName=${APP_NAME} \
			-X ${APP_NAME}/internal/constants.BuildTimestamp=$$(date --utc +'%Y-%m-%dT%H:%M:%S') \
			-X ${APP_NAME}/internal/constants.Version=$$(git rev-parse --abbrev-ref HEAD)-$$(git rev-parse HEAD | head -c 6) \
		" \
		-o "${BIN_PATH}/${APP_NAME}${BINARY_EXT}" \
		"${CMD_PATH}/${APP_NAME}"
	@cp "${BIN_PATH}/${APP_NAME}${BINARY_EXT}" "${BIN_PATH}/${APP_NAME}_$$(go env GOOS)_$$(go env GOARCH)${BINARY_EXT}"

deps:
	@echo "updating dependencies..."
	@go mod tidy
	@go mod vendor

docs-swaggo: 
	@echo "generating documentation package..."
	swag init \
		--generalInfo "./internal/api/api.go" \
		--output "${DOCS_OUTPUT_PATH}" \
		--parseInternal \
		--parseDependency \
		--parseDepth 8

image:
	@echo "building image ${IMAGE_URL}:${IMAGE_TAG}..."
	docker build -t ${IMAGE_URL}:${IMAGE_TAG} .

test:
	@echo "running tests..."
	@go test -v -coverpkg=./... -coverprofile=./tests/cover.out ./...
	@go tool cover -func ./tests/cover.out
	@go tool cover -html ./tests/cover.out -o ./tests/cover.html

install-swaggo:
	@echo "installing swag from ${SWAGGO_URL}..."
	go get -u ${SWAGGO_URL}/cmd/swag@latest

install-swaggo-ci:
	@echo "installing swag from ${SWAGGO_URL}..."
	go install ${SWAGGO_URL}/cmd/swag@latest

```

## Useful packages

<table><thead><tr><th width="148.33333333333331">Name</th><th width="310">Description</th><th data-hidden>Link</th></tr></thead><tbody><tr><td>cobra</td><td>CLI structure</td><td><a href="https://github.com/spf13/cobra">https://github.com/spf13/cobra</a></td></tr><tr><td>gofiber</td><td>HTTP server</td><td><a href="https://gofiber.io/">https://gofiber.io/</a></td></tr><tr><td>logrus</td><td>Logger utilities tool</td><td><a href="https://github.com/sirupsen/logrus">https://github.com/sirupsen/logrus</a></td></tr><tr><td>viper</td><td>Configuration management</td><td><a href="https://github.com/spf13/viper">https://github.com/spf13/viper</a></td></tr></tbody></table>


# Networking cheatsheet

## Get DNS records

Using default DNS resolvers:

```bash
nslookup google.com
```

Using Cloudflare (`1.1.1.1`) DNS resolver:

```bash
nslookup google.com 1.1.1.1
```

## Setup HTTP server with netcat

Run the following to start a HTTP responder on port `5555`:

```bash
while :; do echo -e "HTTP/1.1 200 OK\nContent-Length: 3\n\n ok" | nc -l -p 5555; done
```

To verify TCP connectivity, run the above and then:

```bash
curl localhost:5555
# or
wget -qO - localhost:5555
```

## Verifying port connectivity

To verify that port `443` is open on `google.com`, use:

```bash
nc -zv google.com 443
```

Generic structure of command:

```bash
nc -zv ${HOSTNAME} ${PORT}
```


# Infra security check tools

## DKIM/DMARC check

1. <https://easydmarc.com/tools/dkim-lookup>
2. <https://dmarcly.com/tools/dkim-record-checker>
3. <https://mxtoolbox.com/dkim.aspx>

## SSL ciphers check

Use <https://cryptcheck.fr/>

## SSL certificate registrations

Use <https://crt.sh>

## Security Headers

Use <https://securityheaders.com/>

## TLS versions check

Use <https://www.ssllabs.com/ssltest/>


# Using Ubuntu as a workstation

## Enabling system wake-up from USB device

Run the following to get a list of your USB devices:

```bash
lsusb
```

The output should look like:

```
# ... more ...
Bus 004 Device 003: ID 05e3:0626 Genesys Logic, Inc. USB3.1 Hub
Bus 001 Device 002: ID 1d6b:0003 Linux Foundation 3.0 root hub
# ... more ...
```

In the above output, the hex numbers represent the vendor ID followed by the product ID, eg. `05e3` is the vendor ID (`idVendor`) for Genesys Logic and `0626` is the product ID (`idProduct`).

The files that store configurations for the above can be found at `/sys/bus/usb/devices`. To identify which directory in there contains the desired USB device (you will need to run these as `root`):

```bash
cd /sys/bus/usb/devices;
ls -1 \
  | grep -v '\:' \
  | xargs -I@ sh -c 'printf -- "@ == " && printf -- "$(cat @/idVendor):" && printf -- "$(cat @/idProduct)\n"'
```

The output should look like (intentionally matched with the `lsusb` output):

```
# ... more ...
4-1 == 05e3:0626
usb4 == 1d6b:0003
# ... more ...
```

Match the above output from `lsusb` with the most recent output and note the left value (eg. `4-1` refers to the Genesys Logic device)

Run the following to enable wake-up from the USB device:

```bash
echo enabled > /sys/bus/usb/devices/4-1/power/wakeup;
```

To enable persistence, put the above script in your `/etc/rc.local` as well.


# Message Brokers


# Kafka

## Reference links

## Configuration

### Certifcate Generation

```makefile
KAFKA_ALIAS := localhost
KAFKA_CERTS_PATH := ./.data/kafka/config/certs
KAFKA_CA_KEY_PATH := ${KAFKA_CERTS_PATH}/ca-key
KAFKA_CA_CERT_PATH := ${KAFKA_CERTS_PATH}/ca-cert

KAFKA_CLIENT_CERT_PATH := ${KAFKA_CERTS_PATH}/client-cert
KAFKA_CLIENT_KEY_PATH := ${KAFKA_CERTS_PATH}/client-key
KAFKA_CLIENT_P12_PATH := ${KAFKA_CERTS_PATH}/client.p12

KAFKA_JKS_KEYSTORE_PATH := ${KAFKA_CERTS_PATH}/kafka.keystore.jks
KAFKA_JKS_TRUSTSTORE_PATH := ${KAFKA_CERTS_PATH}/kafka.truststore.jks

kafka-jks: # ref https://www.ibm.com/docs/en/cloud-paks/cp-biz-automation/20.0.x?topic=emitter-preparing-ssl-certificates-kafka
	rm -rf ${KAFKA_CERTS_PATH}/*
	mkdir -p ${KAFKA_CERTS_PATH}
	echo '*' > ${KAFKA_CERTS_PATH}/.gitignore
	echo '!.gitignore' >> ${KAFKA_CERTS_PATH}/.gitignore

	# create certificate authority
	openssl req -new -x509 -keyout ${KAFKA_CA_KEY_PATH} -out ${KAFKA_CA_CERT_PATH} -days 365

	# create client certificate
	openssl req -new -newkey rsa:2048 -nodes -keyout ${KAFKA_CLIENT_KEY_PATH} -out ${KAFKA_CLIENT_CERT_PATH} -days 365
	openssl x509 -req -days 365 -in ${KAFKA_CLIENT_CERT_PATH} -CA ${KAFKA_CA_CERT_PATH} -CAkey ${KAFKA_CA_KEY_PATH} -out ${KAFKA_CLIENT_CERT_PATH} -set_serial 01 -sha256

	# package client data into client keystore
	openssl pkcs12 -export -in ${KAFKA_CLIENT_CERT_PATH} -inkey ${KAFKA_CLIENT_KEY_PATH} -name user > ${KAFKA_CLIENT_P12_PATH}
	keytool -importkeystore -srckeystore ${KAFKA_CLIENT_P12_PATH} -destkeystore ${KAFKA_JKS_KEYSTORE_PATH} -srcstoretype pkcs12 -alias user

	# package certificate authority into server truststore
	keytool -keystore ${KAFKA_JKS_TRUSTSTORE_PATH} -alias CARoot -import -file ${KAFKA_CA_CERT_PATH}

	chmod 644 ${KAFKA_CERTS_PATH}/*
```

## Docker

### Image

{% embed url="<https://hub.docker.com/r/bitnami/kafka/>" %}

### Compose

```yaml
version: "3.7"
services:
  kafka: #
    # image reference: https://hub.docker.com/r/bitnami/kafka/
    image: bitnami/kafka:3.5.1
    environment:
    - KAFKA_CFG_NODE_ID=0
    - KAFKA_CFG_PROCESS_ROLES=controller,broker
    - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@127.0.0.1:9093
    - KAFKA_CFG_LISTENERS=SASL_SSL://:9092,CONTROLLER://:9093
    - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:SASL_PLAINTEXT,SASL_SSL:SASL_SSL
    - KAFKA_CFG_ADVERTISED_LISTENERS=SASL_SSL://:9092
    - KAFKA_CLIENT_USERS=user
    - KAFKA_CLIENT_PASSWORDS=password
    - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
    - KAFKA_CFG_SASL_MECHANISM_CONTROLLER_PROTOCOL=PLAIN
    - KAFKA_CONTROLLER_USER=controller_user
    - KAFKA_CONTROLLER_PASSWORD=controller_password
    - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=SASL_SSL
    - KAFKA_CFG_SASL_MECHANISM_INTER_BROKER_PROTOCOL=PLAIN
    - KAFKA_INTER_BROKER_USER=controller_user
    - KAFKA_INTER_BROKER_PASSWORD=controller_password
    - KAFKA_CERTIFICATE_PASSWORD=password
    - KAFKA_TLS_TYPE=JKS
    ports:
      - '9092:9092'
      - '9093:9093'
    network_mode: host
    volumes: # [] # uncomment this and comment below to remove persistence
      - ./.data/kafka/data:/bitnami/kafka/data
      - ./.data/kafka/config:/bitnami/kafka/config
```


# NATS

## Reference Links

| Description      | URL                                  |
| ---------------- | ------------------------------------ |
| Official website | <https://nats.io/>                   |
| DockerHub image  | <https://hub.docker.com/_/nats>      |
| NATS Go SDK      | <https://github.com/nats-io/nats.go> |
| NATS JS SDK      | <https://github.com/nats-io/nats.js> |
| NATS Python SDK  | <https://github.com/nats-io/nats.py> |
| NKeys repository | <https://github.com/nats-io/nkeys>   |
| NATS CLI tool    | <https://github.com/nats-io/natscli> |

## Configuration

### NKey generation

```makefile
nats-nkey:
	nk -gen user -pubout
```

### Server

This file should be available on the system running NATS. NATS should be started using the `--config` or `-c` flag pointing to the path of this file (eg. `nats-server -c /path/to/this.conf`

```properties
accounts: {
  $SYS: {
    users: [
      # generate using `nk-gen user -pubout`
      {nkey: "..."},
    ]
  }
}

authorization: {
  users: [
    # generate using `nk-gen user -pubout`
    {nkey: "..."},
  ]
}

cluster: {
  name: "example"
}

jetstream: {
  max_memory_store: 2GB
  max_file_store: 8GB
}
```

## Docker

### Image

{% embed url="<https://hub.docker.com/_/nats>" %}

### Compose&#x20;

The following starts NATS using the leanest image base (`scratch`) and with JetStream enabled:

<pre class="language-yaml"><code class="lang-yaml"><strong>version: "3.7"
</strong>services:
  nats: # access with `nats server info`
    # image reference: https://hub.docker.com/_/nats
    image: library/nats:2.9.20-scratch
    entrypoint:
      - /nats-server
      - -js
      - -c
      - /etc/nats/server.conf
    ports:
      - "4222:4222"
      # # enable as needed
      # - "6222:6222"
      # - "8222:8222"
    network_mode: host
    volumes:
      - ./.data/nats/config/server.conf:/etc/nats/server.conf
</code></pre>


# Databases


# MongoDB

## Reference Links

## Configuration

## Docker

### Image

{% embed url="<https://hub.docker.com/_/mongo>" %}

### Compose

```yaml
version: "3.7"
services:
  mongo: # access with `mongosh 'mongodb://user:password@127.0.0.1/database?authSource=admin'`
    # image reference: https://hub.docker.com/_/mongo
    image: library/mongo:6.0.8
    environment:
      MONGO_INITDB_DATABASE: database
      MONGO_INITDB_ROOT_USERNAME: user
      MONGO_INITDB_ROOT_PASSWORD: password
    ports: ["27017:27017"]
    network_mode: host
    volumes: # [] # uncomment this and comment below to remove persistence
      - ./.data/mongodb/data/data/db:/data/db
```

## Debugging/error handling

### Useful links

* [Official MongoDB documentation on exit codes and statuses](https://www.mongodb.com/docs/manual/reference/exit-codes/)

### Exit code 14

#### Keywords

"FileNotOpen", "Failed to open archive file"

#### Explanation

This exit code is formally defined as:

{% code overflow="wrap" %}

```

Returned by MongoDB applications which encounter an unrecoverable error, an uncaught exception or uncaught signal. The system exits without performing a clean shutdown.
```

{% endcode %}

**Exploration**

This error typically implies a system-level issue that prevents MongoDB from starting successfully.

**Known Fixes 1 - MongoDB by Bitnami running in Kubernetes**

This fix addresses an issue where the disk space allocated for MongoDB was filled to 100%

* Update the `statefulset` resource so that the container's `command` is `sleep 1000000`.
* Allow the MongoDB `pod` instances to restart
* Get a shell into each of them and use `df -h` to view the available diskspace
* Observe that `/bitnami/mongodb` is at 100% or near that
* Using MongoDB logs, figure out where it is trying to write to, in this fix, the issue was with the diagnostic data and the following command was run to free up disk space:

```
rm -rf /bitnami/mongodb/data/db/diagnostic.data/*
```

**Reference/useful links**

* <https://stackoverflow.com/questions/68067064/mongodb-failed-to-start-due-to-filenotopen>
*


# MySQL

## Reference Links

## Configuration

## Docker

### Image

{% embed url="<https://hub.docker.com/_/mysql>" %}

### Compose

```yaml
version: "3.7"
services:
  mysql: # access with `mysql -uroot -h127.0.0.1 -P3306 -p`
    # image reference: https://hub.docker.com/_/mysql
    image: library/mysql:8.0.34
    environment:
      MYSQL_PASSWORD: password
      MYSQL_USER: user
      MYSQL_DATABASE: database
      MYSQL_ROOT_PASSWORD: password
    ports: ["3306:3306"]
    network_mode: host
    volumes: # [] # uncomment this and comment below to remove persistence
      - ./.data/mysql/data/var/lib/mysql/data:/var/lib/mysql
```


# PostgreSQL

## Reference Links

## Configuration

## Docker

### Image

{% embed url="<https://hub.docker.com/_/postgres>" %}

### Compose

```yaml
version: "3.7"
services:
  postgres: # access with `psql -Uuser -h127.0.0.1 -p5432 database -W`
    # image reference: https://hub.docker.com/_/postgres
    image: library/postgres:15.3-alpine
    environment:
      POSTGRES_PASSWORD: password
      POSTGRES_USER: user
      POSTGRES_DB: database
    ports: ["5432:5432"]
    network_mode: host
    volumes: # [] # uncomment this and comment below to remove persistence
      - ./.data/postgres/data/var/lib/postgres/data:/var/lib/postgresql/data
```


# Redis

## Reference Links

## Configuration

The following configuration is a production-ready `.conf` file which:

1. Binds to 0.0.0.0
2. Disables `default` user
3. Forces use of a password
4. Prevents users from running configuration commands
5. Creates a app user named `user` with password `password` (**change this according to the comments in the file**)

```properties
# security configurations as documented at https://redis.io/topics/security
bind 0.0.0.0
rename-command CONFIG ""

# disable default user
requirepass password
user default off -@all

# setup app user
# to generate the password, run `printf -- 'password' | sha256sum | cut -f 1 -d ' '`
# the following password (after the '#' character) is the sha256 of "password" without the quotes
user user on ~* +ping +client +@read +@write +@set +@list #5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
```

## Docker

### Image

{% embed url="<https://hub.docker.com/_/redis>" %}

### Compose

```yaml
version: "3.7"
services:
  redis: # access with `redis-cli -h 127.0.0.1 -p 6379` and use `auth user password` in the redis tty
    # image reference: https://hub.docker.com/_/redis
    image: library/redis:7.0.12-alpine
    command:
      - redis-server
      - /usr/local/etc/redis/redis.conf
    ports: ["6379:6379"]
    network_mode: host
    volumes: # [] # uncomment and comment below to remove persistence
      - ./.data/redis/config/redis.conf:/usr/local/etc/redis/redis.conf
      - ./.data/redis/data:/data
```


# Kubernetes

## Useful local tools

| Name                 | Description                           | URL                                     |
| -------------------- | ------------------------------------- | --------------------------------------- |
| Pluto                | Finds deprecated Kubernetes Resources | <https://github.com/FairwindsOps/pluto> |
| K9s                  | Very awesome CLI Kubernetes dashboard | <https://k9scli.io/>                    |
| Kubernetes-in-Docker | Run a local Kubernetes cluster        | <https://kind.sigs.k8s.io/>             |
| Helm                 | Orchestrates Kubernetes releases      | <https://helm.sh/>                      |

## Useful cluster services

| Name                         | Description                                              | URL                                                               |
| ---------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------- |
| actions-runner-controller    | Run Github Actions locally in-cluster                    | <https://github.com/actions/actions-runner-controller>            |
| aws-efs-csi-driver           | Enables provisioning of EFS volumes                      | <https://github.com/kubernetes-sigs/aws-efs-csi-driver>           |
| aws-load-balancer-controller | Automates provisioning of AWS load balancers             | <https://github.com/kubernetes-sigs/aws-load-balancer-controller> |
| cert-manager                 | <p>Provisions LetsEncrypt </p><p>certificates</p>        | <https://cert-manager.io/>                                        |
| external-dns                 | Provisions DNS entries                                   | <https://github.com/kubernetes-sigs/external-dns>                 |
| falco                        | Cluster-wide runtime security tool                       | <https://github.com/falcosecurity/falco>                          |
| fluentd                      | Enables global cluster-level logging                     | <https://docs.fluentd.org/quickstart>                             |
| gitlab-runner                | Run Gitlab Pipelines locally in-cluster                  | <https://docs.gitlab.com/runner/install/kubernetes.html>          |
| istio                        | Enables mTLS and also provides Envoy ingress controllers | <https://istio.io/>                                               |
| kube-prometheus              | Enables monitoring of cluster resources                  | <https://github.com/prometheus-operator/kube-prometheus>          |
| nginx-ingress-controller     | Provides Nginx-based ingress controllers                 | <https://docs.nginx.com/nginx-ingress-controller/>                |


# Standard resources cheatsheet

## ClusterRole

<details>

<summary>Basic example</summary>

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: {{ include "template.name" . }}
  labels:
    {{- include "template.labels" . | nindent 4 }}
rules:
  - apiGroups: [""]
    resources:
      - configmaps
      - endpoints
      - namespaces
      - nodes
      - pods
      - pods/logs
      - replicationcontrollers
      - serviceaccounts
      - services
    verbs: &readOnly
      - get
      - watch
      - list
  - apiGroups: [""]
    resources:
      - secrets
    verbs: &listOnly
      - list
  - apiGroups: ["apps"]
    resources:
      - controllerrevisions
      - deployments
      - daemonsets
      - replicasets
      - statefulsets
    verbs: *readOnly
  - apiGroups: ["autoscaling"]
    resources:
      - autoscaling
    verbs: *readOnly
  - apiGroups: ["batch"]
    resources:
      - cronjobs
      - jobs
    verbs: *readOnly
  - apiGroups: ["networking.k8s.io"]
    resources:
      - ingresses
    verbs: *readOnly
  - apiGroups: ["policy"]
    resources:
      - podsecuritypolicies
    verbs: *readOnly
```

</details>

## ClusterRoleBinding

<details>

<summary>Basic example for ServiceAccount &#x3C;> ClusterRole</summary>

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: {{ include "template.name" . }}
  labels:
    {{- include "template.labels" . | nindent 4 }}
subjects:
- kind: ServiceAccount
  name: {{ .Values.serviceAccount.name }}
  namespace: {{ .Values.serviceAccount.namespace }}
roleRef:
  kind: ClusterRole
  name: {{ .Values.clusterRole.name }}
  apiGroup: rbac.authorization.k8s.io
```

</details>

## ConfigMap

<details>

<summary>Basic example with hardcoded values</summary>

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "template.name" . }}
  labels:
    {{- include "template.labels" . | nindent 4 }}
data:
  var1: value1
  var2: value2
```

</details>

<details>

<summary>For use with a .Values.config.env hashmap</summary>

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "template.fullname" . }}-env
  labels:
    {{- include template.labels" . | nindent 4 }}
  annotations:
    helm.sh/hook: pre-install,pre-upgrade
    helm.sh/hook-weight: "-10"
    helm.sh/resource-policy: keep
data:
  {{ toYaml .Values.config.env | nindent 2 }}
```

</details>

## CronJob

<details>

<summary>Basic example</summary>

```yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: {{ include "template.name" . }}
  labels:
    {{- include "template.labels" . | nindent 4 }}
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      successfulJobsHistoryLimit: 3
      failedJobsHistoryLimit: 5
      template:
        spec:
          containers:
          - name: {{ include "template.name" . }}
            image: "{{ .Values.image.repository }}:{{ required "The image.tag must be specified to deploy this" .Values.image.tag }}"
            imagePullPolicy: IfNotPresent
            args:
            - /bin/sh
            - -c
            - date; echo "Hello!"
          restartPolicy: OnFailure
```

</details>

## DaemonSet

<details>

<summary>Basic example</summary>

```yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: {{ include "template.fullname" . }}
  labels:
    {{- include "template.labels" . | nindent 4 }}
spec:
  selector:
    matchLabels:
      {{- include "template.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      {{- with .Values.podAnnotations }}
      annotations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      labels:
        {{- include "template.selectorLabels" . | nindent 8 }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "template.serviceAccountName" . }}
      securityContext:
        {{- toYaml .Values.podSecurityContext | nindent 8 }}
      initContainers:
        - name: {{ .Chart.Name }}-info-retrieval
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          command:
          - sh
          - -c
          - |
            
          env:
          - name: NODENAME
            valueFrom:
              fieldRef:
                fieldPath: spec.nodeName
          volumeMounts:
          - name: node-data
            mountPath: /data
      containers:
      - name: {{ .Chart.Name }}
        securityContext:
          {{- toYaml .Values.securityContext | nindent 12 }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        command:
          - sh
          - -c
          - |
            while :; do echo "HTTP/1.1 200 OK
            Content-Type: text/html; charset=UTF-8
            Server: nc
            Content-Length: 13

            hello world
            " | nc -l 12345; done;
        env:
        - name: NODENAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        resources:
          {{- toYaml .Values.resources | nindent 12 }}
        volumeMounts:
        - name: node-data
          mountPath: /data
      volumes:
      - name: node-data
        hostPath:
          path: /node-data
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
```

</details>

## Deployment

<details>

<summary>Basic template with Secret, ConfigMap, and PVC resources</summary>

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "template.name" . }}
  labels:
    {{- include "template.labels" . | nindent 4 }}
spec:
  replicas: 1
  selector:
    matchLabels:
      {{- include "template.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "template.labels" . | nindent 8 }}
    spec:
      containers:
      - name: {{ include "template.name" . }}
        image: "{{ .Values.image.repository }}:{{ required "The image.tag must be specified to deploy this" .Values.image.tag }}"
        imagePullPolicy: Never
        ports:
        - name: http
          containerPort: {{ .Values.service.port }}
          protocol: TCP
        envFrom:
        - secretRef:
            name: {{ include "template.fullname" . }}-env
            optional: false
        - configMapRef:
            name: {{ include "template.fullname" . }}-env
            optional: false
        resources:
          limits:
            memory: 25Mi
            cpu: 75m
          requests:
            memory: 20Mi
            cpu: 50m
        volumeMounts:
        - name: dir-mount
          mountPath: /path/to/dir/
        - name: file-mount
          mountPath: /path/to/file.ext
          subPath: file.ext
      volumes:
      - name: dir-mount
        secret:
          defaultMode: 440
          secretName: {{ include "template.fullname" . }}-dir
      - name: file-mount
        secret:
          defaultMode: 440
          secretName: {{ include "template.fullname" . }}-file
```

</details>

## Ingress

<details>

<summary>Basic example</summary>

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "template.name" . }}
  labels:
    {{- include "template.labels" . | nindent 4 }}
spec:
  {{- if .Values.ingress.tls }}
  tls:
    {{- range .Values.ingress.tls }}
    - hosts:
        {{- range .hosts }}
        - {{ . | quote }}
        {{- end }}
      secretName: {{ .secretName }}
    {{- end }}
  {{- end }}
  rules:
    {{- range .Values.ingress.hosts }}
    - host: {{ .host | quote }}
      http:
        paths:
          {{- range .paths }}
          - path: {{ .path }}
            pathType: Prefix
            backend:
              service:
                name: {{ .serviceName }}
                port:
                  number: {{ .servicePort }}
          {{- end }}
    {{- end }}
```

</details>

## Secret

<details>

<summary>Basic example</summary>

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: {{ include "template.name" . }}
  labels:
    {{- include "template.labels" . | nindent 4 }}
type: Opaque
# either this ...
data:
  var1: d293IHlvdSBhY3R1YWxseSBkZWNvZGVkIHRoaXM=
  var2: YSBjdXJpb3VzIG9uZSwgeW91IGFyZQ==
# ... or this ...
stringData:
  var1: hello world
  var2: "12345"
```

</details>

## Service

<details>

<summary>Basic example</summary>

```yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ include "template.name" . }}
  labels:
    {{- include "template.labels" . | nindent 4 }}
spec:
  selector:
    {{- include "template.labels" . | nindent 4 }}
  ports:
    - protocol: TCP
      port: {{ .Values.service.port }}
      targetPort: {{ .Values.service.port }}
```

</details>

## ServiceAccount

<details>

<summary>Basic example</summary>

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ include "template.name" . }}
  labels:
    {{- include "template.labels" . | nindent 4 }}
```

</details>

## StatefulSet

<details>

<summary>Basic example</summary>

```yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ include "template.fullname" . }}
  labels:
    {{- include "template.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.statefulSet.replicaCount }}
  selector:
    matchLabels:
      {{- include "test.selectorLabels" . | nindent 6 }}
  serviceName: {{ include "template.fullname" . }}-statefulset
  template:
    metadata:
      {{- with .Values.podAnnotations }}
      annotations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      labels:
        {{- include "template.selectorLabels" . | nindent 8 }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "template.serviceAccountName" . }}
      securityContext:
        {{- toYaml .Values.podSecurityContext | nindent 8 }}
      containers:
        - name: {{ .Chart.Name }}
          securityContext:
            {{- toYaml .Values.securityContext | nindent 12 }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          command:
            - sh
            - -c
            - |
              while :; do echo "HTTP/1.1 200 OK
              Content-Type: text/html; charset=UTF-8
              Server: nc
              Content-Length: 13

              hello world
              " | nc -l 12345; done;
          ports:
            - containerPort: 12345
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          volumeMounts:
            - name: test-default
              mountPath: /mnt/test-default
            - name: test-statefulset
              mountPath: /mnt/test-statefulset
      volumes:
        - name: test-default
          emptyDir: {}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
  volumeClaimTemplates:
  - apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      labels:
        {{- include "template.labels" . | nindent 8 }}
      name: test-statefulset
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi
      volumeMode: Filesystem
```

</details>


# Istio

## EnvoyFilter

<details>

<summary>With removal of sensitive HTTP headers</summary>

The following EnvoyFilter resource removes the HTTP headers:

1. `x-envoy-decorator-operation`: reveals the internal hostname to external networks
2. `x-envoy-upstream-service-time`: reveals that Envoy is being used
3. `server`: reveals the server technology being used

Create and apply the following EnvoyFilter using `kubectl apply -f ./path/to/envoyfilter.yaml`:

```yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  # name: {{ include "istio.fullname" . }}-header-removal
  # labels:
  #   {{- include "istio.labels" . | nindent 4 }}
  name: x-envoy-header-removal
  namespace: web-app
spec:
  configPatches:
  - applyTo: NETWORK_FILTER
    match:
      context: SIDECAR_INBOUND
      listener:
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
    patch:
      operation: MERGE
      value:
        typed_config:
          '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          server_header_transformation: PASS_THROUGH
  - applyTo: HTTP_ROUTE
    match:
      context: SIDECAR_INBOUND
    patch:
      operation: MERGE
      value:
        decorator:
          propagate: false
        response_headers_to_remove:
          - "server"
          - "x-envoy-decorator-operation"
          - "x-envoy-upstream-service-time"
          - "x-powered-by"
```

</details>

## VirtualService

<details>

<summary>Basic example</summary>

```yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: {{ include "template.name" . }}
  annotations:
    external-dns.alpha.kubernetes.io/target: {{ .Values.loadBalancer.hostname }}
  labels:
    {{- include "template.labels" . | nindent 4 }}
spec:
  hosts:
  {{ range .Values.config.istio.ingress.urls -}}
    - {{ . | quote }}
  {{ end }}
  gateways:
    - {{ .Values.istio.gateway.namespace }}/{{ .Values.istio.gateway.name }}
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: {{ .Values.istio.ingress.hostname }}
        port:
          number: {{ .Values.service.port }}
```

</details>


# Prometheus

## PodMonitors

<details>

<summary>Basic example</summary>

```yaml
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
  name: {{ include "template.fullname" . }}
  labels:
    {{- include "template.labels" . | nindent 4 }}
spec:
  selector:
    matchLabels:
      {{- include "template.selectorLabels" . | nindent 6 }}
  podMetricsEndpoints:
  - port: http
```

</details>

## ServiceMonitors

<details>

<summary>Basic example</summary>

```yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: {{ include "template.fullname" . }}
  labels:
    {{- include "template.labels" . | nindent 4 }}
spec:
  endpoints:
  - interval: 5s
    port: http
  selector:
    matchLabels:
      {{- include "template.selectorLabels" . | nindent 6 }}
```

</details>


# Workflow Orchestrators


# Airflow

## Configuring Airflow to use Azure as OAuth Provider

```python
# ...

from airflow.www.fab_security.manager import AUTH_DB
from airflow.www.fab_security.manager import AUTH_OAUTH

# ...

AUTH_TYPE = AUTH_OAUTH
OAUTH_PROVIDERS = [
  { 
    'name':'azure',
    'token_key':'access_token',
    'icon':'fa-windows',
    'remote_app': {
      "api_base_url": "https://login.microsoftonline.com/__todo_azure_tenant_id__",
      "request_token_url": None,
      'request_token_params': {
        'scope': 'openid email profile'
      },
      "access_token_url": "https://login.microsoftonline.com/__todo_azure_tenant_id__/oauth2/v2.0/token",
      "access_token_params": {
        'scope': 'openid email profile'
      },
      "authorize_url": "https://login.microsoftonline.com/__todo_azure_tenant_id__/oauth2/v2.0/authorize",
      "authorize_params": {
        'scope': 'openid email profile'
      },
      'client_id': '__todo_azure_client_id__',
      'client_secret': '__todo_azure_client_secret__',
      'jwks_uri': 'https://login.microsoftonline.com/common/discovery/v2.0/keys'
    }
  }
]
AUTH_USER_REGISTRATION_ROLE = "Public"
AUTH_USER_REGISTRATION = True
AUTH_ROLES_SYNC_AT_LOGIN = True
AUTH_ROLES_MAPPING = {
    "${ADMIN_GROUP_NAME_IN_AZURE_GROUPS}": ["Admin"],
    "${OP_GROUP_NAME_IN_AZURE_GROUPS}": ["Op"],
    "${USER_GROUP_NAME_IN_AZURE_GROUPS}": ["User"],
    "${VIEWER_GROUP_NAME_IN_AZURE_GROUPS}": ["Viewer"]
}

class AzureCustomSecurity(AirflowSecurityManager, LoggingMixin):
  def get_oauth_user_info(self, provider, response=None):
    if provider == "azure":
      self.log.debug("Azure response received : {0}".format(response))
      id_token = response["id_token"]
      self.log.debug(str(id_token))
      me = self._azure_jwt_token_parse(id_token)
      self.log.debug("Parse JWT token : {0}".format(me))
      parsed_token = {
        "name": me["name"],
        "email": me["email"],
        "first_name": me["given_name"],
        "last_name": me["family_name"],
        "id": me["oid"],
        "username": me["preferred_username"],
        "upn": me["oid"],
        "role_keys": me["roles"],       
      }
      return parsed_token
    else:
      return {}

SECURITY_MANAGER_CLASS = AzureCustomSecurity
```


# Terraform


# AWS


# Kubernetes IAM roles

The following Terraform file creates an IAM role for a Kubernetes ServiceAccount to use so that a workload resource is able to assume that IAM role via the ServiceAccount

```hcl
variable "cluster_oidc_issuers" {
  description = "List of ODIC issuers of clusters which the application will be deployed into, this is needed for allowing the IAM role to be assumed by the cluster's workload resources"
  type = list(object({
    id = string,
    k8s_namespace = string,
    k8s_service_account = string,
    region = string,
  }))
}

locals {
  name_k8s = "iam_role_name"
}

data "aws_caller_identity" "current" {}

# ref https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role
resource "aws_iam_role" "k8s" {
  name               = local.name_k8s
  assume_role_policy = data.aws_iam_policy_document.allow_eks_to_assume_role.json
  inline_policy {
    name   = local.name_k8s
    policy = data.aws_iam_policy_document.main.json
  }
}

# ref https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document
data "aws_iam_policy_document" "allow_eks_to_assume_role" {
  dynamic "statement" {
    for_each = toset(var.cluster_oidc_issuers)
    content {
      actions = ["sts:AssumeRoleWithWebIdentity"]
      principals {
        type = "Federated"
        identifiers = [
          "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/oidc.eks.${statement.value.region}.amazonaws.com/id/${statement.value.id}"
        ]
      }
      condition {
        test     = "StringEquals"
        variable = "oidc.eks.${statement.value.region}.amazonaws.com/id/${statement.value.id}:aud"
        values   = ["sts.amazonaws.com"]
      }
      condition {
        test     = "StringEquals"
        variable = "oidc.eks.${statement.value.region}.amazonaws.com/id/${statement.value.id}:sub"
        values   = [
          "system:serviceaccount:${statement.value.k8s_namespace}:${statement.value.k8s_service_account}",
        ]
      }
    }
  }
}

# ref https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document
data "aws_iam_policy_document" "main" {
  statement {
    sid = "Placeholder"
    actions = [
      "sts:GetCallerIdentity",
    ]
    resources = [
      "*"
    ]
  }
}

output "iam_role_k8s" {
  description = "Details of the IAM role to bind to the application's ServiceAccount resource"
  value       = {
    arn = aws_iam_role.k8s.arn,
    assume_role_policy = jsondecode(aws_iam_role.k8s.assume_role_policy),
    inline_policy = [for inline_policy in aws_iam_role.k8s.inline_policy : jsondecode(inline_policy.policy)],
    name = aws_iam_role.k8s.name,
  }
}
```


# Overview of Climbing

I was first hooked onto climbing in the late 2000s. Gave it up after a few years and only restarted my climbing journeys again in 2023. Here's an overview of what climbing is from my current knowledge.

## Getting started with climbing

The easiest way to try out climbing is either top-rope or bouldering.

Top-rope in general is safer ever since gyms began installing auto-belay systems which allows you to fall/get down safely as long as you don't do anything intentionally stupid while on the wall.

Bouldering minimally requires you to learn how to fall properly, it's not difficult, but the fear of falling from a height and having to perform movements to make a safe landing can make it prohibitive to some.

Both of these disciplines of climbing require you to own little to no gear. Shoes and harnesses can be rented at every gym. Chalk may not be rentable so you might wanna bring a more experienced friend along or else get your own for less than $10 USD.

In general, gym entries cost $15-30 USD in cities and $10-$20 USD in outskirt areas. Shoe and harness rentals generally cost $3-5 USD and if chalk is available for renting, it's usually close to free.

If you're in Singapore, checkout the map of climbing gyms and shops in Singapore in this knowledge garden at [Singapore](/climbing/singapore).&#x20;

## Types of climbing

### Bouldering

<figure><img src="/files/SGl1MCgOXh8O1TfgzQmv" alt=""><figcaption><p>Photo by <a href="https://unsplash.com/@tofanteo?utm_content=creditCopyText&#x26;utm_medium=referral&#x26;utm_source=unsplash">Tofan Teodor</a> on <a href="https://unsplash.com/photos/a-young-man-is-climbing-on-a-climbing-wall-WvleCyCQE0o?utm_content=creditCopyText&#x26;utm_medium=referral&#x26;utm_source=unsplash">Unsplash</a></p></figcaption></figure>

Bouldering is a type of climbing that features short routes that require lots more strength and power than endurance. Since it doesn't require any skills/certifications or excessive gear, bouldering is one of the most accessible forms of climbing. All you need is shoes (rentable at most gyms) and chalk (only if your palms get sweaty) and you're good to go.

Bouldering walls in gyms are generally 3-4 meters high with huge cushy mattresses beneath them. Learning how to fall properly is the only essential skill here. In general you'll also be climbing back down more often than jumping down especially if you're past the age of 30.

### Top-rope

<figure><img src="/files/MNXFl66UjNyNIuIrlGtQ" alt=""><figcaption><p>Photo by <a href="https://unsplash.com/@bezer?utm_content=creditCopyText&#x26;utm_medium=referral&#x26;utm_source=unsplash">Adam Bezer</a> on <a href="https://unsplash.com/photos/a-man-climbing-up-the-side-of-a-climbing-wall-sKu8A-2zGk4?utm_content=creditCopyText&#x26;utm_medium=referral&#x26;utm_source=unsplash">Unsplash</a></p></figcaption></figure>

This type of climbing features a high wall - generally 15 - 30 meters in gyms - where you'll attach one end of the rope to yourself to catch your fall. Most gyms these days have auto-belays which are generally safe and have never failed as long as the rope is attached to you properly. The alternative is getting a friend who's around the same weight to belay you.

### Lead&#x20;

<figure><img src="/files/2ssy5ggkJpY0uSyLSJMz" alt=""><figcaption><p>Photo by <a href="https://unsplash.com/@alightproduction_by_sabrinawendl?utm_content=creditCopyText&#x26;utm_medium=referral&#x26;utm_source=unsplash">Sabrina Wendl</a> on <a href="https://unsplash.com/photos/man-wearing-gray-tank-top-climbing-on-wall-h2NlwNkA2h8?utm_content=creditCopyText&#x26;utm_medium=referral&#x26;utm_source=unsplash">Unsplash</a></p></figcaption></figure>

Lead climbs are similar in nature to top-rope climbs featuring high walls. The difference here is the presence of many clips along the routes which you have to attach your own rope to as you climb up. Lead climbing in gyms generally require you have some kind of certification or prove your skills in person.

### Trad

<figure><img src="/files/ZD9A9YPpoQewH03MOUBX" alt=""><figcaption><p>Photo by <a href="https://unsplash.com/@brookanderson?utm_content=creditCopyText&#x26;utm_medium=referral&#x26;utm_source=unsplash">Brook Anderson</a> on <a href="https://unsplash.com/photos/man-in-black-t-shirt-and-orange-shorts-climbing-brown-rock-formation-during-daytime-1TwUoSkVEdQ?utm_content=creditCopyText&#x26;utm_medium=referral&#x26;utm_source=unsplash">Unsplash</a></p></figcaption></figure>

Trad climbing is an upgraded form of lead climbing where the clips are placed by you instead of being already available on the wall. Trad climbers are typically seen with many tools hanging on their waist which they will use to insert clips along the route as they climb upwards.

### Free solo

Free solo-ing is climbing high routes without gear and is more of a death-wish than a discipline since the cost of failure is extremely high.

## Climbing grades

Most gyms have their own different grading systems. I've heard it's mainly to encourage beginners - which form the bulk of customers - to not see the internationally recognised grades which can be demoralising for some. Each jump of an international grade requires easily 1-2 years of consistent practice which can be too daunting for some.

The three main grading systems that's internationally recognised are the Yosemite Decimal System (YDS), Font Scale, and V-Scale. The YDS scale is used almost exclusively for outdoor routes while the Font and V scales are more commonly used in general.

The conversion chart is as follows for the Font-Scale/V-Scale:

<figure><img src="/files/R540DD9iZKHI2JwgyjVD" alt=""><figcaption></figcaption></figure>

The conversion chart is as follows for YDS/Font Scale:

<figure><img src="/files/mR1CkZpFTrz9AX3pnHuO" alt=""><figcaption><p><a href="https://www.sportrock.com/post/understanding-climbing-grades">https://www.sportrock.com/post/understanding-climbing-grades</a></p></figcaption></figure>

## Climbing resources

### Apps

{% embed url="<https://griptonite.io/>" %}

### Youtube Channels

Quintissential watching is this channel on technique progression by Movement for Climbers:

{% embed url="<https://www.youtube.com/@movementforclimbers>" %}

And here's some other channels which delve into slightly more advanced aspects of climbing:

{% embed url="<https://www.youtube.com/@CatalystClimbing>" %}

{% embed url="<https://www.youtube.com/@EmilAbrahamsson>" %}

{% embed url="<https://www.youtube.com/@hannahmorrisbouldering>" %}

{% embed url="<https://www.youtube.com/@LatticeTraining>" %}

{% embed url="<https://www.youtube.com/@magmidt>" %}


# Singapore

## Map of gyms and shops

{% embed url="<https://www.google.com/maps/d/edit?mid=1RsrFFZAjJpX5zqLsOgNzGEcx4z2XTLw&usp=sharing>" %}

## Small local businesses

| Name   | URL                          |
| ------ | ---------------------------- |
| ALL.EZ | <https://shopee.sg/allez_sg> |


# Introduction to Crypto


# Web3 terminology

Web3 communities are usually filled with jargon and abbreviations that can be super confusing at first. Hopefully this page helps you get started in participating in these niche communities more quickly:

* **Airdrop** - When a project sends you a token(s) for free at their own cost
* **Alpha** - Early (usually unpublished/undocumented) information/news that's usually accessible only to a select group of individuals based on participation/membership tier
* **Anon** - Refers to you, the reader (eg "Hi Anon"). Originates from individuals in crypto generally preferring to be anonymous for safety reasons
* **APR** - Annual Percentage Rate - Percentage gains based on you staking tokens for a year and receiving interest continuously based on a reward tokens' emissions. You usually want to assess a contract's potential performance via this instead of its APY.
* **APY** - Annual Percentage Yield - Percentage gains based on APR but assuming reinvestment of gains into the staked tokens. You usually want to assess a contract's performance via its APR and not this.
* **Bagholder** - Usually used in a negative context to refer to someone who held a bag of tokens past it's pump
* **Blockchain** - A databsae technology that allows for immutable and reproducible state and state transitions that serves as a digital ledger of transactions
* **Bridge** - A set of contracts which allow for cross-chain asset transfer
* **Bridging** - Term that Web3 communities use to refer to the act of bringing IRL value to its members via ownership of a project's tokens
* **CDC** - Crypto Dot Com - A CEX
* **CEX** - Centralised EXchange - Exchanges which abstract away chain interactions from users and provide an interface that's more familiar to TradFi traders
* **Coin** - Native currency of a chain (eg $SOL on Solana, $ETH on Ethereum, $MATIC on Polygon, $CRO on Cronos)
* **Cold Wallet** - A term that originally meant a wallet meant for long-term storage of digital assets but which is also loosely used when referring to hardware wallets
* **Custodial Wallet** - A wallet assigned to you by CEXes so that you don't have to maintain your own seed phrase. Custodial Wallets are generally considered unsafe due to CEXes being able to halt transfers at any time. Also see Non-Custodial Wallets
* **dApps** - Distributed Apps - The web-accessible interface which interacts with smart contracts via Metamask
* **DeFi** - Decentralised Finance - Finance based on a distributed blockchain technology
* **DEX** - Decentralised EXchange - these are exchanges which are a collection of contracts which have been deployed on a chain and made available to you via a website interface which you can use Metamask with
* **Diamond Hand** - Refers to an individual who held tokens that mooned, often through massive dumps in price. Can also be used as a verb.
* **Dump** - When the price of a token drops suddenly and drastically
* **ERC** - Ethereum Request for Comment - A proposal for a standard that when approved becomes a function interface for a smart contract
* **ERC-20** - Most commonly seen interface for exchange-tradable fungible tokens
* **ERC-721** - A common interface for NFTs where there is only one copy of each token represented by a single digital asset
* **ERC-1155** - A common interface for NFTs that supports multiple copies of each token. Each token is represented by a single digital asset, but has multiple copies where each can be owned by different wallets
* **Etherscan** - The most popular free tool used to view transactions on the Ethereum blockchain
* **EVM** - Ethereum Virtual Machine - Usually seen when referring to a chain as "EVM-compatible" which means that code written for the Ethereum chain is also deployable on that chain
* **Fiat** - TradFi Money
* **Fiat farming** - Doing your day job
* **Frens** - "Friends" but in crypto-speak
* **GM** - Good Morning - Web3's collectively agreed upon way of saying "hello" - because it's always morning somewhere in the world and crypto is 24/7
* **GN** - Good Night - Web3's collectively agreed upon way of saying "bye" - because it's always night somewhere in the world and crypto is 24/7
* **Hardware Wallet** - A wallet where the seed phrase is stored on a separate device. Common choices for a hardware wallet are Ledger, Keystone, and Trezor.
* **Hodl** - A misspelling of "hold" and now used to refer to buying a token and not selling it despite price falling
* **Hot Wallet** - A term that originally referred to a wallet that is used for day-to-day transactions in crypto. Hot Wallets can be expected to be compromised sooner or later due to the many scams and malicious dApps that exist, to keep things SAFU, put your assets onto a Cold Wallet
* **LFG** - Let's Fucking Go!
* **LP** - Liquidity Pool - A type of contract that stores equivalent fiat values of two tokens which are designed for use by DEXes to perform token trades
* **Metamask** - The most popular browser extension and app for accessing Web3 dApps
* **MM** - Metamask (see Metamask)
* **Moon** - When the price of a token pumps anywhere from 10-100x
* **NFT** - Non-Fungible Token - A token belonging to a smart contract that implements either the ERC-721 or ERC-1155 standard
* **Non-Custodial Wallet** - A wallet where you have full ownership of your seed phrase
* **NGMI** - Not Gonna Make It - A phrase that Web3 communities use when it comes to dissing an individual's trading habits or a project's roadmap when they think it's bad
* **OpSec** - Operational Security - Refers to personal security measures that individuals participating in Web3 take to stay SAFU from scammers
* **Proof of Reserves** - CEXes way of proving to people that fiat money stored with them is SAFU. Gained popularity after the fall of the FTX CEX
* **Pump** - When the price of a token goes up suddenly and drastically
* **PND** - Pump and Dump - A common scam involving a scammer creating a token, creating lots of hype around it to pump the price, and then liquidating the "team's allocation", resulting in a dump
* **Roadmap** - Basically the project's investor pitch to investors. Usually a set of milestones and deliverables defined by the team which have rewards at every milestone to acquire early investors
* **SAFU** - "Safe" but in crypto-speak
* **Shitcoin** - Loosely used to refer to any token that doesn't meet an investor's expectation or have any utility
* **Skem** - "Scam" but in crypto-speak
* **Smart contract** - A program written for deployment onto a blockchain (eg. Solidity for Ethereum, Move for Aptos)
* **Solidity** - A programming language used for writing smart contracts on EVM-compatible blockchains
* **Staking** - Storing your tokens with a project in return for a guaranteed APR on a project's "reward" token
* **Touching Grass** - Going out to participate in IRL activities instead of Discord/Telegram/Twitter
* **TradFi** - Traditional Finance - The IRL money system
* **Utility** - Refers to things you can do with a token. Generally over-promised and under-delivered by project teams
* **VC** - Voice Chat - Refers to the voice chat in a Discord server. Usually seen as "Join VC now"
* **WAGMI** - We Are Gonna Make It - A phrase that Web3 communities use as a rallying cry in the hope that the bull market comes again
* **Web3** - Web 3.0 - The internet with identity proof and digital asset ownership mechanisms baked in


# Beginner's Guide to Personal Operational Security

Commonly termed as OpSec within the industry, operational security refers to the practice of securing your identity and other digital assets while being online.

OpSec is a graded approach guided by risk management principles and implemented as-needed based on individuals' context. Determining the "right" level of security tends to be a unique process to yourself since security measures always introduce inconvenience and you want to be sure that these inconveniences are justified.

## Background

This piece unlike the many others that you can easily Google, assumes that you are a consumer of technologies instead of a user/administrator/compliance person and is meant for the everyday person to learn how to apply some sensible OpSec to their daily life online.

This piece is informed by 2+ years spent in the crypto/Web3 world where scams are more common than legitimate deals, and from being a platforms engineer in the cybersecurity industry. This means while it's not advice from a cybersecurity professional, it should still be useful enough for most people who aren't targetted by state-sponsored h4x0rs.

## Planning

While I would prefer to avoid being cheesy/cliched, the 5Ws1H thingyis really a good model for framing concepts. When planning for security measures to implement, it is useful to consider:

1. Who - Who might be interested in assuming my identity or stealing my assets?
2. Why - Why might they be interested?
3. What - What assets might be valuable to an attacker?
4. When - When will an attacker attempt to carry out an attack?
5. How - How could an attacker gain access to an asset?

The objective is to identify high risk + impact compromises which can happen, determine the most-likely path of an attacker, and then find measures to ensure that an attacker cannot carry out their plan.

### Who

The "Who" are known in the industry as Threat Actors and can range from:

1. Petty individuals who you've offended
2. Spray-and-pray scammers
3. Scam syndicates
4. Corporate espionage
5. State-sponsored hackers

In most situations and for most people, you'd only be worried about 1-3. If you're someone with lots of access rights in your organisation, you might have to consider 4 if you know the financial benefit for a successful attack could be huge.

### Why

The motivation behind an attack is likely linked to the "who". In majority of cases, it would be financial gain. In some cases, this could also be to inflict damage on you or your organisation which can happen with corporate espionage.

For most people who are online, financial gain for the attacker is the reason they get compromised.

### What

We talk about assets but what are they exactly? Assets refer to any artifact which can themselves be the goal of an attack, or an artifact which can be used to enable another attack.

Let's take an example of an asset as a social media account associated with you. Access to the social media account rarely grants the attacker any tangible financial gain. Successfully asking your friends for money via that account realises the financial gain. Selling access to that account could also result in financial gain for the attacker.

On the other hand, consider your banking credentials as an asset. Access to these credentials grants an attacker direct access to withdraw your money.

### When

When would an attacker strike depends entirely on the persona of the attacker. If you're being targeted by well-resourced teams, you'd probably need to be on alert 24/7. For most of us though, attacks will most likely begin their journey as someone initiating a conversation with you which starts the process known as "social engineering".

### How

How could an attacker get to you or your assets? This could be availability of your email address which is legitimately on a company website, or made available via an email dump on a pastebin document. It could also be your phone number&#x20;

Questions to assess yourself in general

1. Who is likely to want to own my identity or assets? In order of resources availability and capabilities, this can range from petty individuals you've offended to spray and pray scammers to scam syndicates to corporate spies to state-sponsored hackers
2. Which of my accounts enable access to real life assets? Think bank accounts that can be used to withdraw/transfer money, seed phrases that enable access to a crypto wallet
3. Which of my accounts are "trusted"? Think social media/email accounts which can be used to contact your friends on behalf of you, or any accounts that you may use to "Sign in with ..." on other sites
4. Where are credentials to my accounts stored? Think pieces of paper, metal storage cabinets, password managers, in my mind.

## Everyday threats

#### Avoid exposing PIIs online

As far as possible, avoid entering full names, birthdays, email addresses, phone numbers, and physical addresses into online databases. Always assume that the system has zero security and that these can be accessed by multiple people (in reality, they can and will be accessed by vendors and advertising partners - read your Privacy Policy!) who can sell your data to an attacker.

Prevention is better than cure and one way of preventing a social engineering attack on yourself is to obsecure your real identity online.

Asides from government and banking accounts, all other websites or services you use simply need to know "a name" which doesn't necessarily have to be your real name or even an alias you are known to friends by.

* Email addresses can be obscured by using an email forwarding service (iOS provides one for free) or by using burner emails.
* If you're constantly using services online, consider obtaining a second number with a prepaid card or a low-priced plan which you can easily replace. Use this secondary number for non-critical services to keep your primary number safe
* Physical addresses usually only need to be revealed for deliveries. To keep yourself safe, pair it with a name that isn't yours so that your name cannot be linked to your physical address

#### Password protect your device

#### Use long passwords

Up to the 2010s we've been advised to change passwords regularly and to use complex passwords that include symbols and numbers and mixed-case alphabets. Both pieces of advice are dated.

* Complex passwords are plain inconvenient, resulting in users continuously forgetting their passwords. Inconvenience breeds resentment and eventually users will find a way to game the system while breaking actual security. Think `P@ssw0rd` which satisifies the commonly found minimum-8-character rule, an uppercase letter, a symbol, and a number - but which is also definitely in a rainbow table.
* Being forced to change your password regularly results in passwords like `password2023q1` or `password2023q2` which defeats the purpose of changing passwords regularly.

A scientifically better way of creating passwords is length. A long password you can remember is better than a complex one which you will forget. Alone it is not very secure, but together with multi-factor authentication (more on this later), this provides real security for your account.

Check this page out to see what I mean regarding password lengths:

{% embed url="<https://bitwarden.com/password-strength/>" %}

<figure><img src="/files/1wzpmzJNnCi4oEjnV81b" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/nD8sxxltxEesC0izpx9u" alt=""><figcaption></figcaption></figure>

Length matters because how passwords are cracked are typically through bruteforcing (running through every combination of characters) or rainbow tables (a giant file containing a list of common passwords or hashes of common passwords).

* Every additional alphabet (read: easily remembered) character exponentially increases the processing needed by a bruteforcing tool by a factor of 26
* Every number, if numbers are introduced, increases the minimum processing needed by a bruteforcing tool by a factor of 36 (accounting for alphabets too)

#### Use a password manager

Using a password manager ensures you don't leave scraps of paper with your username/password lying around. Sure, it becomes a single source of failure, but decent password managers will

#### Enable multi-factor authentication (MFA)

#### Enable authentication on your MFA

#### Enable auto-HTTPS

#### Use a VPN

VPNs&#x20;

#### Destroy cookies on browser close

#### Install/enable an antivirus solution

#### Install/enable a firewall solution

#### Verify URLs before clicking

#### Remove query parameters from URLs before sharing/accessing

## Corporate threats

#### Install a company administered MDM solution

#### Physically separate your MFA or use a hardware token

In theory, MFA should always be on a separate device so that it's "air-gapped". In reality, this rarely happens especially with accounts like personal email or social media accounts. When addressing corporate-target threats though, this becomes a necessity more than a good additional security measure.

#### Perform full encryption of your hard drive

#### Use company-administered VPN on public Wi-Fi

Commercial VPNs provide protection of your identity, with corporate threats, you can be sure your attacker isn't a scriptkiddie or a spray-and-pray attacker. Your attack will be an envelope with your name on it. This means

#### Disable USB access for non-HID devices on your computer

#### Destroy cookies on browser close

#### Authenticate someone with out-of-band channels

When receiving requests to perform critical actions, always confirm

#### Use different browsers for accessing information of different security levels

## State threats

#### Use Signal for messaging

#### Use burner phones for calls and messages


