For Python specifically, I used the ZScaler instructions combined with direnv and a custom script to set up individual Python projects to allow the certificate impersonation not to break all communication.

NOTE: This is all not beautiful (nor is SSL interception) but putting it out there in case it helps someone.

This post is for a Homebrew and pyenv type of situation. It could be adapted for other environments. Other assumptions:

- ZScaler root CA at
`~/etc/zscaler_root_ca.pem`

- Homebew ca-certificates at
`/usr/local/Cellar/ca-certificates`

- Python venv is at
`venv`

The first step is setting up direnv as described on the README there.

Then I created a script to update the Homebrew root certificate store with the ZScaler root CA cert:

```
# ~/bin/ca_bundle
# This file is to be sourced, not run directly.
prefix="/usr/local/Cellar/ca-certificates"
latest="$(ls -1tr /usr/local/Cellar/ca-certificates)"
base_path="$prefix/$latest/share/ca-certificates"
export REQUESTS_CA_BUNDLE="$base_path/cacert.pem"
# This confusing array of paths taken from:
# https://help.zscaler.com/zia/adding-custom-certificate-application-specific-trust-store#pip
export CERT_PATH="$REQUESTS_CA_BUNDLE"
export CERT_DIR="$base_path"
export SSL_CERT_FILE=${CERT_PATH}
export SSL_CERT_DIR="$base_path"
echo "DEBUG: Latest bundle at $REQUESTS_CA_BUNDLE"
if grep -i zscaler $REQUESTS_CA_BUNDLE; then
echo "ZScaler cert already present, skipping"
else
cat <<END >> $REQUESTS_CA_BUNDLE
ZScaler Interception Shenanigans
================================
$(cat ~/etc/zscaler_root_ca.pem)
END
fi
```

Then, in the base project dir, create a `.envrc`

and make use what we created above:

```
# Activate the virtual environment as usual
source venv/bin/activate
# Set environment variables to override CA certificate store and add ZScaler
# root certificate to the store
source $HOME/bin/ca_bundle
```

This works for me in that `pip-sync`

can do its job. Of course this may have mistakes or will not work for you.

`id`

parameter that all CDK resources take. I now realise
this is just a handle for identifying resources in the eventual CloudFormation template tree. It For example:

```
bucket = s3.Bucket(self,
"MyFirstBucket",
bucket_name="my-bucket-name",
versioned=True)
```

Here `MyFirstBucket`

is the CloudFormation handle used in the resource tree in CloudFormation AWS console. However,
`bucket_name`

sets the actual S3 bucket name; in this example `s3://my-bucket-name`

.

- Flannel (L3): “L3 network fabric”. Allocates IP ranges. Doesn’t forward packets, done by…
- VXLAN (L2 in L3, a bit like VLANs over multiple switches except now it’s a broadcast domain routed via IP): Encapsulates Ethernet frames in packets to be sent over the network to other hosts. Creates single logical network that “overlays” the physical and makes it look like the pods are all in the same, directly connected network.
- kube-proxy (L4): “Reflects” TCP ports around to make it look like, externally to a pod, that services are running on their standard ports. For example, an app might bind to port 8443 in its container, which is then exposed to some arbitrary port on the host OS. Then the K8s Service will route from the regular port 443 to this arbitrary port and onto the actual service in the container. Kube-proxy is the one “listening” on port 443 then reflecting to the container exposed port. This is commonly done in the Linux kernel via iptables redirections (maybe some NAT too?).

GraphQL is a query language.

See the quick introduction in the docs but, essentially, we are working with

- Connection: Like SQL these let you query related objects in the same call. E.g.
`associatedPullRequests`

is a connection between a branch (ref) and a pull request. - Nodes: Generic term for an object. We are interested in the
`pullrequest`

object - Edge: Represents connections between nodes e.g. pull request(s) that are associated with a PR

Here is the query we are using to find which PR(s) are associated with a given commit (only using the first one found so far):

```
{
repository(name: "example-repo", owner: "ftclausen") {
ref(qualifiedName:"topic/some_branch") {
associatedPullRequests(first: 1) {
edges {
node {
number
title
baseRef {
name
}
}
}
}
}
}
}
```

This then returns the following data

```
{
"data": {
"repository": {
"ref": {
"associatedPullRequests": {
"edges": [
{
"node": {
"number": 55,
"title": "This is an example pull request",
"baseRef": {
"name": "main"
}
}
}
]
}
}
}
}
}
```

As best I understand `edges`

will always be an array. Finally this can be imagined graphically which I find useful:

We then pull the above data in and use it as needed

Given GraphQL data varies per type I followed this very helpful guide guide to generate types for GraphQL responses instead of manually doing it. Please see that for a concrete example.

We have three different ways pull requests are represented depending on the context:

- Pull Request Event: When a new PR is created we get a pull_request event payload
- REST API Pull Request response
- Pull Request from GraphQL response

So that is something to be aware of when working with TypeScript. I had to make my own meta-object to unify this for my purposes; this meta-object extracted just what I needed out of each one of the above types.

]]>- $\beta_j$ - In the rewritten 1.15 where $\beta_0 = \beta = -1$ and $\beta_1 = \gamma = 1$
- $\beta_m$ - In the binary representation or expansion. I.e. $(\beta_m \beta_{m-1} … \beta_1 \beta_0)_2$. As in $\beta_m$ is either a $0$ or a $1$.

Note on book context: Page 15, from just before equation 1.15.

About re-using the cyclical bit-shifting “magical solution” to the Josephus problem in a more generalised context. Firstly, the magical solution they are talking about:

\[J((b_mb_{m-1}...b_1b_0)_2) = (b_{m-1}...b_1b_0b_m)_2\]They then ask

Does the generalised Josephus recurrence admit of such magic? Sure, why not?

They then go on to, very cleverly, rewrite generalised equation in a new form that allows us to look at it in a different way. Firstly, the original generalised equation 1.11:

\[\begin{align} f(1) &= \alpha \\ f(2n) &= 2f(n) + \beta\text{,} \ \ \ \ \ \ \text{for } n \geq 1\text{;} \\ f(2n+1) &= 2f(n) + \gamma\text{,} \ \ \ \ \ \ \text{for } n \geq 1\text{.} \ \end{align}\]Then the rewritten one as 1.15

\[\begin{align} f(1) &= \alpha; \\ f(2n + j) &= 2f(n) + \beta, \ \ \ \ \ \ \text{for}\ j=0,1\ \text{and}\ n\geq 1 \\ \end{align}\]Now they say

If we let $\beta_0 = \beta$ and $\beta_1 = \gamma$

Which **I think** means: if we encounter a binary $0$ ($\beta_0)$ in the number then we substitute the $\beta = -1$
*from the generalised form 1.11* equation. Otherwise, if we encounter a binary $1$ ($\beta_1$) then we substitute the
$\gamma = 1$ also from the generalised equation 1.11.

It is unfortunate that they overload $\beta$ in the generalised equation 1.11, rewritten generalised equation 1.15, and in the expansion notation $\beta_m$ where $m$ is the position/power of two in the binary number. I think that is the root of my confusion.

The table presented on page 16 (of the physical book, second edition) is very enlightening in demonstrating the rewritten (1.15) equation is used:

\[\begin{array} n &= &(&1 &1 &0 &0 &1 &0 &0 &)_2 &= 100 \\ \hline f(n) &= &(&1 &1 &-1 &-1 &1 &-1 &-1 &)_2 \\ \end{array}\]Where:

- $\beta = -1$ is applied to $\beta_0$ i.e. binary $0$.
- $\gamma = 1$ is applied to $\beta_1$ i.e. binary $1$.

Applying above wherever we find a binary 1 convert to decimal with the appropriate power of two $m$ as in $2^m$ then multiply by $1$. Likewise, wherever we find a binary 0, convert to decimal with appropriate power of two $m$ as in $2^m$ then multiply by $-1$. Doing the above leads us to:

\[+64 +32 -16 -8 +4 -2 -1 = 73\]So J(100) = 73

]]>- azure-devops-node-api - This looked promising but ultimately I could not get it to work for updating work items. Judging by the silence in my Stackoverflow question it seems updating Work Items via azure-devops-node-api is not something anyone has tried?
- azure-devops-extension-api - This one seemed promising but has no README nor examples. It was too deep a rabbit hole to go down right now.

Thus I settled on typed-rest-client which seemed relatively straight forward to use.

I’ll show some snippets of how I fetched a Work Item and then how to update a custom field (or any field really). For my
use case I just created a toy type to store just what I was interested in rather than try and pull down an official type
from the monster `azure-devops*`

repos above (🐇). If I find a more “official” way of doing it then I’ll update this
post.

Given the labynth of types azure-devops-node-api and azure-devops-extension-api have I decided to just make my own as follows:

```
export interface ADOWorkItemLite {
readonly id: number,
readonly fields: ADOWorkItemFields
}
export interface ADOWorkItemFields {
"Custom.SomeField": string // Can also be another, non-custom field
}
```

```
// Note: I can probably get the PAT handler from type-rest-client as well
import * as ado from 'azure-devops-node-api'
import * as restClient from 'typed-rest-client'
const authHandler = ado.getPersonalAccessTokenHandler(process.env.PAT)
const client: restClient.RestClient = new restClient
.RestClient('example-user-agent', 'https://dev.azure.com', [authHandler])
/**
* Where:
* myorg - Your organisation name
* project - Your URL escaped project name
* 1234 - The ADO ticket being requested
* 7.0 - The ADO API version (7.0 as of March 2023)
*/
const location = '/myorg/project/_apis/wit/workitems/1234?api-version=7.0'
return client.get<ADOWorkItemLite>(location).then(response => {
const workItem = response.result as ADOWorkItemLite
return workItem
})
```

For this I introduced a new type, the HTTP PATCH update payload:

```
export interface ADOWorkItemUpdatePayload {
/**
* What we're updating, usually "add"
*/
op: string,
/**
* The Work Item path e.g. /fields/SomeField
*/
path: string,
/**
* The value given to the item on the path
*/
value: string
}
```

Then the initial parts are the same as getting a work item above but, after that, we apply the update:

```
import * as ado from 'azure-devops-node-api'
import * as restClient from 'typed-rest-client'
const authHandler = ado.getPersonalAccessTokenHandler(process.env.PAT)
const client: restClient.RestClient = new restClient
.RestClient('example-user-agent', 'https://dev.azure.com', [authHandler])
const location = '/myorg/project/_apis/wit/workitems/1234?api-version=7.0'
const patchContents: ADOWorkItemUpdatePayload = {
op: "add",
path: "/fields/Custom.SomeField",
value: updatedWorkItem.fields['Custom.SomeField']
}
return client.update<ADOWorkItemUpdatePayload>(location, [patchContents], {
additionalHeaders: {
"Content-Type": 'application/json-patch+json'
}
}).then(results => {
console.log(`${results.statusCode} - Updated work item ${updatedWorkItem.id}`)
})
```

For me, this worked. As mentioned I should use the auth handler from type-rest-client to remove the dependency on azure-devops-node-api.

]]>In this example they propose the problem of

Write numbers using each of the ten digits exactly once so that the sum of the numbers is exactly 100

Then the book goes through some attempts where we relax the conditions.

Here are some examples from the book where they try to get close by relaxing the condition that they need to add up to 100:

\[19 + 28 + 37 + 46 + 50 = 180\]or

\[19 + 28 + 30 + 7 + 6 + 5 + 4 = 99\]close but no cigar.

Here we get 100 but we are repeating some numbers. It would seem it is not possibles to satisfy both conditions (uniqueness and adding up to 100) at once.

We need to *prove that it is impossible to solve both conditions at the same time*.

Now we go about being optimistic and try to satisfy both conditions. The book does this by being very clever I thought;
simply pretend both conditions *are* true even though we suspect they are not.

Some ideas:

- There are ten figures, 0 to 9, and each one must appear at least once
- The sum of just those figures is $0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9$ is $45$

Now comes the bit I found confusing:

Some of these figures denote units and some of them tens

I thought they meant in the above some of single figures. They were all units but then I realised the author meant
*while adding to get 100* not in the sum to make $45$. Continuing:

It takes a little sagacity to hit upon the idea that the

sum of the figures denoting tensmay be of some importance.

This also caused me some confusion (a common state for me to be in) but using the examples above:

\[19 + 28 + 30 + 7 + 6 + 5 + 4 = 99\]Here the sum of the tens figures are

\[10 + 20 + 30\]From the numbers 19, 28, 30. Another example from above:

\[19 + 28 + 31 + 7 + 6 + 5 + 4 = 100\]Where the tens are, again,

\[10 + 20 + 30\]We can see these give us the *most progress* to 100 (i.e. to 60). The others (part of the 45) get us the rest of the
way. Now $60 + 45 = 105$ which is obviously too much so we need to subtract the ones we’ve already used to get to 60.
This, then, finally brings us to:

\[10t + (45 - t) = 100\]It takes a little sagacity to hit upon the idea that the

sum of the figures denoting tensmay be of some importance.In fact, let $t$ stand for this sum. Then the sum of the remaining figures, denoting units, is $45 - t$. Therefore the numbers in the set must be

Breaking that down

- $10t$ - This is the 60 talked about above. Or, $6 * 10$ because as represented here.
- $45 - t$ - This is the remaining units figured

So, altogether, this should add to 100.

Solving for $t$ we get

\[t = \frac{55}{9}\]Here we get the absurdum beacause, of course, $t$ should be an integer otherwise it won’t add to 100. Thus we can’t satisfy both conditions at once.

]]>In How to Solve It they go through a series of equivalent problems to find the eventual solution. So we may not know the solution to our original problem A but we can go through B, C, D, etc. until we find a solution we know or that can be directly “seen”.

The book goes through solving this equation with the unknown $x$

\[x^4 - 13x^2 + 36 = 0\]Since I am bootstrapping my mathematical knowledge I found the book’s treatment a bit terse without many explanations of how they got to each step. I eventually figured out that they are trying to massage the equation into a state where you can apply the perfect square rule. This makes things much simpler to reason about and thus find the solution.

As mentioned above this is

\[x^4 - 13x^2 + 36 = 0\]And our task is to find $x$. Or, more specifically, where the graph intercepts the $x$ axis at $0$. The steps are essentially a set of quadratic equation equivalence conversions.

My slow self needed to ponder this one for a while. In any case, B ends up being

\[(2x^2)^2 - 2(2x^2)13 + 144 = 0\]This had me baffled for a while but I, eventually, saw that they are multiplying by increasing powers of two. I don’t know if this technique has a name or how they arrived at doing this first step but, to write it another way,

\[\begin{align} 2^0\cdot(2x^2)^2 - 2^1\cdot(2x^2)13 + 2^2\cdot36 &= 0 \\ (2x^2)^2 - 2(2x^2)13 + 144 &= 0 \end{align}\]For those who know quadratics quite well can probably see where they are going with these steps but, anyway, I was still baffled at this stage but at least it could see the manipulation easily enough:

\[\begin{align} (2x^2)^2 - 2(2x^2)13 + 144 + 25 &= 0 + 25 \\ (2x^2)^2 - 2(2x^2)13 + 169 &= 25 \end{align}\]This makes things much more compact through, as I eventually figured out, the perfect square rule! To remind the forgetful like me it is: $(a - b)^2 = a^2 - 2ab + b^2$. There is also a $a + b$ version.

\[\begin{align} (2x^2)^2 - 2(2x^2)13 + 169 &= 25 \\ (2x^2 - 13)^2 &= 25 \end{align}\]The more compact form gives us a better handle on things. This is a key step to solve this more easily. The following steps are using this step to find the four solutions.

Here we take the square root and find a sort-of solution that still needs some work. Let’s call it the IKEA solution:

\[\begin{align} (2x^2 - 13)^2 &= 25 \\ 2x^2 - 13 &= \pm5 \end{align}\]Let’s isolate the $x^2$ like it has covid:

\[\begin{align} 2x^2 - 13 &= \pm5 \\ x^2 &= \frac{13\pm5}{2} \end{align}\]Now we take the square root remembering our $\pm$:

\[\begin{align} x^2 &= \frac{13\pm5}{2} \\ x &= \pm\sqrt{\frac{13\pm5}{2}} \end{align}\]Now can see what the concrete answers are: $x = 3$ or $-3$ or $2$ or $-2$.

]]>High level the steps are

- Generate key or use old key again (not shown in this post)
- Create CSR and get back signed certificate (not shown in this post)
- Also get CA intermediate certificate - usually comes back with signed certificate (this step might be optional)
- Concatenate your certificate with CA intermediate and all root certs
- Create PKCS12 file
- Create Java keystore file
- Import as Kubernetes secret

Usually an up to date ca-certificates package will have a file containing all root certs. This package is available on Homebrew or on Linux. E.g. it could be in:

- Homebrew -
`<homebrew base>var/homebrew/linked/ca-certificates/share/ca-certificates/cacert.pem`

- Linux - Commonly somewhere like
`/etc/ssl/certs/ca-certificates.crt`

- Or get it from Firefox or some other browser

Once you have the ca-certificates run

```
cat CA.crt /usr/local/homebrew/var/homebrew/linked/ca-certificates/share/ca-certificates/cacert.pem > allcerts.crt
```

Where `CA.crt`

is your CA intermediate certificate, be sure to also check the
path to the `ca-certificates`

full CA certificate list. It will probably be
different to above.

Then create a PKCS12 file containing your own cert as well as the CA chain:

```
openssl pkcs12
-export -in example.com.crt \
-inkey ~/somewhere/safe/example.key \
-out example.p12 \
-name <some alias> \
-CAfile allcerts.crt -caname root \
-chain
```

Replace the example names as appropriate and also `<some alias>`

to something meaningful. E.g. if using Tomcat this is commonly set to `tomcat`

.

```
keytool -deststoretype JKS \
-importkeystore \
-srckeystore example.p12 \
-srcstoretype PKCS12 \
-srcstorepass changeme \
-alias tomcat # Or any other alias that makes sense for your use case \
-deststorepass changeme \
-destkeypass changeme \
-destkeystore example.keystore
```

Note - `keytool`

complains if you use JKS due to it being deprecated but it has the greatest compatibility e.g. with Gradle.

First save the current secret (if there is one) so that it can be referred to later if need be

```
kubectl get -o yaml secret example-keystore > ~/somewhere/safe
```

Then delete the current secret (coordinating as appropriate if this is a heavily used one)

```
kubectl delete secret example-keystore
```

Because you cannot update secrets in-place.

```
kubectl create secret generic example-keystore --from-file example.keystore
```

Once this is done systems (like services in K8s) that mount in that secret volume will be able to use the updated cert. Deployments may need scaling down and then up again or just delete the pods so they are recreated.

Thanks to these for helping me along my way:

- https://stackoverflow.com/a/8224863/1300307
- https://superuser.com/questions/1142555/openssl-p12-generation-failing-with-ca-bundle-chain-option

See the book for full context; I’ll just cover the induction related steps.

The recurrence comes in two flavours: odd and even. The complete recurrences are thus:

\[\begin{align} J(1) &= 1;\\ J(2n) &= 2J(n) - 1, \ \ \ \ \text{for n} \geq 1\\ J(2n+1) &= 2J(n) + 1, \ \ \ \ \text{for n} \geq 1\\ \end{align}\]And the closed form is as follows

\[J(2^m+\ell)=2\ell+1\]The book goes on to give an example of the even induction

$J(2^m+\ell)=2J(2^{m-1} + \ell/2) - 1 = 2(2\ell/2 + 1) - 1 = 2\ell + 1$

by (1.8) and the induction hypothesis; this is exactly what we want.

What confused me at first was the leap

\[2J(2^{m-1} + \ell/2) - 1 = 2(2\ell/2 + 1) - 1\]This is the key induction step - this is not done via algebraic manipulation. We are substituting the closed form that is equivalent to $J(n)$. Our closed form proposal is actually for $2J(n)-1=2l+1$ so we need to refit it to be for $J(n)$. Thus $l$ is in the previous power of two block (remember $J(n)$ not $2J(n)$) and half as big: $l/2$. So the $J(n)$ closed form is $2l/2 +1$.

So we plug that in and then he next step, however, *is done* via algebra:

Completing the even induction.

Credit: I was stuck on this until helped out by the ever helpful Brian M. Scott on Math Stackexchange. I will repeat that here and then do my own (simplified) take on thereafter. First Brian’s reply:

Your’re not applying the recurrence for the odd case correctly. Suppose that $2n + 1 = 2^m + \ell$, where $0 \leq \ell \leq 2^m$. The recurrence is $J(2n + 1) = 2J(n) + 1$, and

\[\begin{align} J(2^m + \ell) &= 2\left(2^{m-1} + \frac{\ell - 1}{2}\right) + 1 \\ &= 2\left(\frac{2(\ell - 1)}{2} + 1 \right) + 1 \\ &= 2\ell + 1 \end{align}\]nhere is $\frac12(2^m + \ell - 1)$, soas desired

I will do some colour highlighting and ever more verbose explanation for my future self here. The core is we want to convert $2n + 1$ into $n$ hence dividing by two and subtracting one:

\[\frac{2(n + 1)}{2} - 1 = n\]We apply these operations during the induction step to turn $2\ell + 1$ into
$\frac{2(\ell -1)}{2} + 1$. Notice the division by two and subtraction by one?
And now with gusto and in context of the induction step where we “insert” the
closed form representing *n* into the recurrence “framework” (substitution
highlighted in blue):

Giving us what we want.

]]>