An offline PKI enhances security by physically isolating the certificate
authority from network threats. A YubiKey is a low-cost solution to store a
root certificate. You also need an air-gapped environment to operate the root
CA.
Offline PKI backed up by 3 YubiKeys
This post describes an offline PKI system using the following components:
2 YubiKeys for the root CA (with a 20-year validity),
1 YubiKey for the intermediate CA (with a 5-year validity), and
It is possible to add more YubiKeys as a backup of the root CA if needed. This
is not needed for the intermediate CA as you can generate a new one if
the current one gets destroyed.
The software part
offline-pki is a small Python application to manage an offline PKI.
It relies on yubikey-manager to manage YubiKeys and cryptography for
cryptographic operations not executed on the YubiKeys. The application has some
opinionated design choices. Notably, the cryptography is hard-coded to use NIST
P-384 elliptic curve.
The first step is to reset all your YubiKeys:
$ offline-pkiyubikeyreset
This will reset the connected YubiKey. Are you sure? [y/N]: yNew PIN code:Repeat for confirmation:New PUK code:Repeat for confirmation:New management key ('.' to generate a random one):WARNING[pki-yubikey] Using random management key: e8ffdce07a4e3bd5c0d803aa3948a9c36cfb86ed5a2d5cf533e97b088ae9e629INFO[pki-yubikey] 0: Yubico YubiKey OTP+FIDO+CCID 00 00INFO[pki-yubikey] SN: 23854514INFO[yubikit.management] Device config writtenINFO[yubikit.piv] PIV application data reset performedINFO[yubikit.piv] Management key setINFO[yubikit.piv] New PUK setINFO[yubikit.piv] New PIN setINFO[pki-yubikey] YubiKey reset successful!
Then, generate the root CA and create as many copies as you want:
$ offline-pkicertificateroot--permittedexample.com
Management key for Root X:Plug YubiKey "Root X"...INFO[pki-yubikey] 0: Yubico YubiKey CCID 00 00INFO[pki-yubikey] SN: 23854514INFO[yubikit.piv] Data written to object slot 0x5fc10aINFO[yubikit.piv] Certificate written to slot 9C (SIGNATURE), compression=TrueINFO[yubikit.piv] Private key imported in slot 9C (SIGNATURE) of type ECCP384Copy root certificate to another YubiKey? [y/N]: yPlug YubiKey "Root X"...INFO[pki-yubikey] 0: Yubico YubiKey CCID 00 00INFO[pki-yubikey] SN: 23854514INFO[yubikit.piv] Data written to object slot 0x5fc10aINFO[yubikit.piv] Certificate written to slot 9C (SIGNATURE), compression=TrueINFO[yubikit.piv] Private key imported in slot 9C (SIGNATURE) of type ECCP384Copy root certificate to another YubiKey? [y/N]: n
Then, you can create an intermediate certificate with offline-pki yubikey
intermediate and use it to sign certificates by providing a CSR to offline-pki
certificate sign. Be careful and inspect the CSR before signing it, as only the
subject name can be overridden. Check the documentation for more details.
Get the available options using the --help flag.
The hardware part
To ensure the operations on the root and intermediate CAs are air-gapped,
a cost-efficient solution is to use an ARM64 single board computer. The Libre
Computer Sweet Potato SBC is a more open alternative to the well-known
Raspberry Pi.1
Libre Computer Sweet Potato SBC, powered by the AML-S905X SOC
I interact with it through an USB to TTL UART converter:
$ tio/dev/ttyUSB0
[16:40:44.546] tio v3.7[16:40:44.546] Press ctrl-t q to quit[16:40:44.555] Connected to /dev/ttyUSB0GXL:BL1:9ac50e:bb16dc;FEAT:ADFC318C:0;POC:1;RCY:0;SPI:0;0.0;CHK:0;TE: 36574BL2 Built : 15:21:18, Aug 28 2019. gxl g1bf2b53 - luan.yuan@droid15-szset vcck to 1120 mvset vddee to 1000 mvBoard ID = 4CPU clk: 1200MHz[ ]
The Nix glue
To bring everything together, I am using Nix with a Flake providing:
a package for the offline-pki application, with shell completion,
a development shell, including an editable version of the offline-pki application,
a NixOS module to setup the offline PKI, resetting the system at each boot,
a QEMU image for testing, and
an SD card image to be used on the Sweet Potato or another ARM64 SBC.
# Execute the application locally
nixrungithub:vincentbernat/offline-pki----help
# Run the application inside a QEMU VM
nixrungithub:vincentbernat/offline-pki\#qemu
# Build a SD card for the Sweet Potato or for the Raspberry Pi
nixbuild--systemaarch64-linuxgithub:vincentbernat/offline-pki\#sdcard.potato
nixbuild--systemaarch64-linuxgithub:vincentbernat/offline-pki\#sdcard.generic
# Get a development shell with the application
nixdevelopgithub:vincentbernat/offline-pki
The key for the root CA is not generated by the YubiKey. Using an
air-gapped computer is all the more important. Put it in a safe with the
YubiKeys when done!
If you ve done anything in the Kubernetes space in recent years, you ve most likely come across the words Service Mesh . It s backed by a set of mature technologies that provides cross-cutting networking, security, infrastructure capabilities to be used by workloads running in Kubernetes in a manner that is transparent to the actual workload. This abstraction enables application developers to not worry about building in otherwise sophisticated capabilities for networking, routing, circuit-breaking and security, and simply rely on the services offered by the service mesh.In this post, I ll be covering Linkerd, which is an alternative to Istio. It has gone through a significant re-write when it transitioned from the JVM to a Go-based Control Plane and a Rust-based Data Plane a few years back and is now a part of the CNCF and is backed by Buoyant. It has proven itself widely for use in production workloads and has a healthy community and release cadence.It achieves this with a side-car container that communicates with a Linkerd control plane that allows central management of policy, telemetry, mutual TLS, traffic routing, shaping, retries, load balancing, circuit-breaking and other cross-cutting concerns before the traffic hits the container. This has made the task of implementing the application services much simpler as it is managed by container orchestrator and service mesh. I covered Istio in a prior post a few years back, and much of the content is still applicable for this post, if you d like to have a look.Here are the broad architectural components of Linkerd:The components are separated into the control plane and the data plane.The control plane components live in its own namespace and consists of a controller that the Linkerd CLI interacts with via the Kubernetes API. The destination service is used for service discovery, TLS identity, policy on access control for inter-service communication and service profile information on routing, retries, timeouts. The identity service acts as the Certificate Authority which responds to Certificate Signing Requests (CSRs) from proxies for initialization and for service-to-service encrypted traffic. The proxy injector is an admission webhook that injects the Linkerd proxy side car and the init container automatically into a pod when the linkerd.io/inject: enabled is available on the namespace or workload.On the data plane side are two components. First, the init container, which is responsible for automatically forwarding incoming and outgoing traffic through the Linkerd proxy via iptables rules. Second, the Linkerd proxy, which is a lightweight micro-proxy written in Rust, is the data plane itself.I will be walking you through the setup of Linkerd (2.12.2 at the time of writing) on a Kubernetes cluster.Let s see what s running on the cluster currently. This assumes you have a cluster running and kubectl is installed and available on the PATH.
On most systems, this should be sufficient to setup the CLI. You may need to restart your terminal to load the updated paths. If you have a non-standard configuration and linkerd is not found after the installation, add the following to your PATH to be able to find the cli:
export PATH=$PATH:~/.linkerd2/bin/
At this point, checking the version would give you the following:
$ linkerd version Client version: stable-2.12.2 Server version: unavailable
Setting up Linkerd Control PlaneBefore installing Linkerd on the cluster, run the following step to check the cluster for pre-requisites:
kubernetes-api -------------- can initialize the client can query the Kubernetes API
kubernetes-version ------------------ is running the minimum Kubernetes API version is running the minimum kubectl version
pre-kubernetes-setup -------------------- control plane namespace does not already exist can create non-namespaced resources can create ServiceAccounts can create Services can create Deployments can create CronJobs can create ConfigMaps can create Secrets can read Secrets can read extension-apiserver-authentication configmap no clock skew detected
linkerd-version --------------- can determine the latest version cli is up-to-date
Status check results are
All the pre-requisites appear to be good right now, and so installation can proceed.The first step of the installation is to setup the Custom Resource Definitions (CRDs) that Linkerd requires. The linkerd cli only prints the resource YAMLs to standard output and does not create them directly in Kubernetes, so you would need to pipe the output to kubectl apply to create the resources in the cluster that you re working with.
$ linkerd install --crds kubectl apply -f - Rendering Linkerd CRDs... Next, run linkerd install kubectl apply -f - to install the control plane.
customresourcedefinition.apiextensions.k8s.io/authorizationpolicies.policy.linkerd.io created customresourcedefinition.apiextensions.k8s.io/httproutes.policy.linkerd.io created customresourcedefinition.apiextensions.k8s.io/meshtlsauthentications.policy.linkerd.io created customresourcedefinition.apiextensions.k8s.io/networkauthentications.policy.linkerd.io created customresourcedefinition.apiextensions.k8s.io/serverauthorizations.policy.linkerd.io created customresourcedefinition.apiextensions.k8s.io/servers.policy.linkerd.io created customresourcedefinition.apiextensions.k8s.io/serviceprofiles.linkerd.io created
Next, install the Linkerd control plane components in the same manner, this time without the crds switch:
$ linkerd install kubectl apply -f - namespace/linkerd created clusterrole.rbac.authorization.k8s.io/linkerd-linkerd-identity created clusterrolebinding.rbac.authorization.k8s.io/linkerd-linkerd-identity created serviceaccount/linkerd-identity created clusterrole.rbac.authorization.k8s.io/linkerd-linkerd-destination created clusterrolebinding.rbac.authorization.k8s.io/linkerd-linkerd-destination created serviceaccount/linkerd-destination created secret/linkerd-sp-validator-k8s-tls created validatingwebhookconfiguration.admissionregistration.k8s.io/linkerd-sp-validator-webhook-config created secret/linkerd-policy-validator-k8s-tls created validatingwebhookconfiguration.admissionregistration.k8s.io/linkerd-policy-validator-webhook-config created clusterrole.rbac.authorization.k8s.io/linkerd-policy created clusterrolebinding.rbac.authorization.k8s.io/linkerd-destination-policy created role.rbac.authorization.k8s.io/linkerd-heartbeat created rolebinding.rbac.authorization.k8s.io/linkerd-heartbeat created clusterrole.rbac.authorization.k8s.io/linkerd-heartbeat created clusterrolebinding.rbac.authorization.k8s.io/linkerd-heartbeat created serviceaccount/linkerd-heartbeat created clusterrole.rbac.authorization.k8s.io/linkerd-linkerd-proxy-injector created clusterrolebinding.rbac.authorization.k8s.io/linkerd-linkerd-proxy-injector created serviceaccount/linkerd-proxy-injector created secret/linkerd-proxy-injector-k8s-tls created mutatingwebhookconfiguration.admissionregistration.k8s.io/linkerd-proxy-injector-webhook-config created configmap/linkerd-config created secret/linkerd-identity-issuer created configmap/linkerd-identity-trust-roots created service/linkerd-identity created service/linkerd-identity-headless created deployment.apps/linkerd-identity created service/linkerd-dst created service/linkerd-dst-headless created service/linkerd-sp-validator created service/linkerd-policy created service/linkerd-policy-validator created deployment.apps/linkerd-destination created cronjob.batch/linkerd-heartbeat created deployment.apps/linkerd-proxy-injector created service/linkerd-proxy-injector created secret/linkerd-config-overrides created
Kubernetes will start spinning up the data plane components and you should see the following when you list the pods:
kubernetes-api -------------- can initialize the client can query the Kubernetes API
kubernetes-version ------------------ is running the minimum Kubernetes API version is running the minimum kubectl version
linkerd-existence ----------------- 'linkerd-config' config map exists heartbeat ServiceAccount exist control plane replica sets are ready no unschedulable pods control plane pods are ready cluster networks contains all pods cluster networks contains all services
linkerd-config -------------- control plane Namespace exists control plane ClusterRoles exist control plane ClusterRoleBindings exist control plane ServiceAccounts exist control plane CustomResourceDefinitions exist control plane MutatingWebhookConfigurations exist control plane ValidatingWebhookConfigurations exist proxy-init container runs as root user if docker container runtime is used
linkerd-identity ---------------- certificate config is valid trust anchors are using supported crypto algorithm trust anchors are within their validity period trust anchors are valid for at least 60 days issuer cert is using supported crypto algorithm issuer cert is within its validity period issuer cert is valid for at least 60 days issuer cert is issued by the trust anchor
linkerd-webhooks-and-apisvc-tls ------------------------------- proxy-injector webhook has valid cert proxy-injector cert is valid for at least 60 days sp-validator webhook has valid cert sp-validator cert is valid for at least 60 days policy-validator webhook has valid cert policy-validator cert is valid for at least 60 days
linkerd-version --------------- can determine the latest version cli is up-to-date
control-plane-version --------------------- can retrieve the control plane version control plane is up-to-date control plane and cli versions match
linkerd-control-plane-proxy --------------------------- control plane proxies are healthy control plane proxies are up-to-date control plane proxies and cli versions match
Status check results are
Everything looks good.Setting up the Viz ExtensionAt this point, the required components for the service mesh are setup, but let s also install the viz extension, which provides a good visualization capabilities that will come in handy subsequently. Once again, linkerd uses the same pattern for installing the extension.
$ linkerd viz install kubectl apply -f - namespace/linkerd-viz created clusterrole.rbac.authorization.k8s.io/linkerd-linkerd-viz-metrics-api created clusterrolebinding.rbac.authorization.k8s.io/linkerd-linkerd-viz-metrics-api created serviceaccount/metrics-api created clusterrole.rbac.authorization.k8s.io/linkerd-linkerd-viz-prometheus created clusterrolebinding.rbac.authorization.k8s.io/linkerd-linkerd-viz-prometheus created serviceaccount/prometheus created clusterrole.rbac.authorization.k8s.io/linkerd-linkerd-viz-tap created clusterrole.rbac.authorization.k8s.io/linkerd-linkerd-viz-tap-admin created clusterrolebinding.rbac.authorization.k8s.io/linkerd-linkerd-viz-tap created clusterrolebinding.rbac.authorization.k8s.io/linkerd-linkerd-viz-tap-auth-delegator created serviceaccount/tap created rolebinding.rbac.authorization.k8s.io/linkerd-linkerd-viz-tap-auth-reader created secret/tap-k8s-tls created apiservice.apiregistration.k8s.io/v1alpha1.tap.linkerd.io created role.rbac.authorization.k8s.io/web created rolebinding.rbac.authorization.k8s.io/web created clusterrole.rbac.authorization.k8s.io/linkerd-linkerd-viz-web-check created clusterrolebinding.rbac.authorization.k8s.io/linkerd-linkerd-viz-web-check created clusterrolebinding.rbac.authorization.k8s.io/linkerd-linkerd-viz-web-admin created clusterrole.rbac.authorization.k8s.io/linkerd-linkerd-viz-web-api created clusterrolebinding.rbac.authorization.k8s.io/linkerd-linkerd-viz-web-api created serviceaccount/web created server.policy.linkerd.io/admin created authorizationpolicy.policy.linkerd.io/admin created networkauthentication.policy.linkerd.io/kubelet created server.policy.linkerd.io/proxy-admin created authorizationpolicy.policy.linkerd.io/proxy-admin created service/metrics-api created deployment.apps/metrics-api created server.policy.linkerd.io/metrics-api created authorizationpolicy.policy.linkerd.io/metrics-api created meshtlsauthentication.policy.linkerd.io/metrics-api-web created configmap/prometheus-config created service/prometheus created deployment.apps/prometheus created service/tap created deployment.apps/tap created server.policy.linkerd.io/tap-api created authorizationpolicy.policy.linkerd.io/tap created clusterrole.rbac.authorization.k8s.io/linkerd-tap-injector created clusterrolebinding.rbac.authorization.k8s.io/linkerd-tap-injector created serviceaccount/tap-injector created secret/tap-injector-k8s-tls created mutatingwebhookconfiguration.admissionregistration.k8s.io/linkerd-tap-injector-webhook-config created service/tap-injector created deployment.apps/tap-injector created server.policy.linkerd.io/tap-injector-webhook created authorizationpolicy.policy.linkerd.io/tap-injector created networkauthentication.policy.linkerd.io/kube-api-server created service/web created deployment.apps/web created serviceprofile.linkerd.io/metrics-api.linkerd-viz.svc.cluster.local created serviceprofile.linkerd.io/prometheus.linkerd-viz.svc.cluster.local created
A few seconds later, you should see the following in your pod list:
The viz components live in the linkerd-viz namespace.You can now checkout the viz dashboard:
$ linkerd viz dashboard Linkerd dashboard available at: http://localhost:50750 Grafana dashboard available at: http://localhost:50750/grafana Opening Linkerd dashboard in the default browser Opening in existing browser session.
The Meshed column indicates the workload that is currently integrated with the Linkerd control plane. As you can see, there are no application deployments right now that are running.Injecting the Linkerd Data Plane componentsThere are two ways to integrate Linkerd to the application containers:1 by manually injecting the Linkerd data plane components 2 by instructing Kubernetes to automatically inject the data plane componentsInject Linkerd data plane manuallyLet s try the first option. Below is a simple nginx-app that I will deploy into the cluster:
Back in the viz dashboard, I do see the workload deployed, but it isn t currently communicating with the Linkerd control plane, and so doesn t show any metrics, and the Meshed count is 0:Looking at the Pod s deployment YAML, I can see that it only includes the nginx container:
Let s directly inject the linkerd data plane into this running container. We do this by retrieving the YAML of the deployment, piping it to linkerd cli to inject the necessary components and then piping to kubectl apply the changed resources.
Back in the viz dashboard, the workload now is integrated into Linkerd control plane.Looking at the updated Pod definition, we see a number of changes that the linkerd has injected that allows it to integrate with the control plane. Let s have a look:
At this point, the necessary components are setup for you to explore Linkerd further. You can also try out the jaeger and multicluster extensions, similar to the process of installing and using the viz extension and try out their capabilities.Inject Linkerd data plane automaticallyIn this approach, we shall we how to instruct Kubernetes to automatically inject the Linkerd data plane to workloads at deployment time.We can achieve this by adding the linkerd.io/inject annotation to the deployment descriptor which causes the proxy injector admission hook to execute and inject linkerd data plane components automatically at the time of deployment.
This annotation can also be specified at the namespace level to affect all the workloads within the namespace. Note that any resources created before the annotation was added to the namespace will require a rollout restart to trigger the injection of the Linkerd components.Uninstalling LinkerdNow that we have walked through the installation and setup process of Linkerd, let s also cover how to remove it from the infrastructure and go back to the state prior to its installation.The first step would be to remove extensions, such as viz.
Day trip on the Olympic Peninsula
TL;DR: drove many kilometres on very nice roads, took lots of
pictures, saw sunshine and fog and clouds, an angry ocean and a calm
one, a quiet lake and lots and lots of trees: a very well spent
day. Pictures at
http://photos.k1024.org/Daytrips/Olympic-Peninsula-2014/.
Sometimes I travel to the US on business, and as such I've been a few
times in the Seattle area. Until this summer, when I had my last trip
there, I was content to spend any extra days (weekend or such) just
visiting Seattle itself, or shopping (I can spend hours in the REI
store!), or working on my laptop in the hotel.
This summer though, I thought - I should do something a bit
different. Not too much, but still - no sense in wasting both days of
the weekend. So I thought maybe driving to Mount Rainier, or something
like that.
On the Wednesday of my first week in Kirkland, as I was preparing my
drive to the mountain, I made the mistake of scrolling the map
westwards, and I saw for the first time the
Olympic Peninsula; furthermore, I was zoomed in enough
that I saw there was a small road right up to the north-west
corner. Intrigued, I zoomed further and learned about
Cape Flattery ( the northwestern-most point of the
contiguous United States! ), so after spending a bit time reading
about it, I was determined to go there.
Easier said than done - from Kirkland, it's a 4h 40m drive (according
to Google Maps), so it would be a full day on the road. I was thinking
of maybe spending the night somewhere on the peninsula then, in order
to actually explore the area a bit, but from Wednesday to Saturday it
was a too short notice - all hotels that seemed OK-ish were fully
booked. I spent some time trying to find something, even not directly
on my way, but I failed to find any room.
What I did manage to do though, is to learn a bit about the area, and
to realise that there's a nice loop around the whole peninsula - the
104 from Kirkland up to where it meets the 101N on the eastern side,
then take the 101 all the way to Port Angeles, Lake Crescent, near
Lake Pleasant, then south toward Forks, crossing the Hoh river, down
to Ruby Beach, down along the coast, crossing the Queets River, east
toward Lake Quinault, south toward Aberdeen, then east towards Olympia
and back out of the wilderness, into the highway network and back to
Kirkland. This looked like an awesome road trip, but it is as long as
it sounds - around 8 hours (continuous) drive, though skipping Cape
Flattery. Well, I said to myself, something to keep in mind for a
future trip to this area, with a night in between. I was still
planning to go just to Cape Flattery and back, without realising at
that point that this trip was actually longer (as you drive on
smaller, lower-speed roads).
Preparing my route, I read about the queues at the Edmonds-Kingston
ferry, so I was planning to wake up early on the weekend, go to Cape
Flattery, and go right back (maybe stop by Lake Crescent).
Saturday comes, I - of course - sleep longer than my trip schedule
said, and start the day in a somewhat cloudy weather, driving north
from my hotel on Simonds Road, which was quite nicer than the usual
East-West or North-South roads in this area. The weather was becoming
nicer, however as I was nearing the ferry terminal and the traffic was
getting denser, I started suspecting that I'll spend a quite a bit of
time waiting to board the ferry.
And unfortunately so it was (photo altered to hide some personal
information):
.
The weather at least was nice, so I tried to enjoy it and simply
observe the crowd - people were looking forward to a weekend relaxing,
so nobody seemed annoyed by the wait. After almost half an hour, time
to get on the ferry - my first time on a ferry in US, yay! But it was
quite the same as in Europe, just that the ship was much larger.
Once I secured the car, I went up deck, and was very surprised to be
treated with some excellent views:
The crossing was not very short, but it seemed so, because of the
view, the sun, the water and the wind. Soon we were nearing the other
shore; also, see how well panorama software deals with waves :P!
And I was finally on the "real" part of the trip.
The road was quite interesting. Taking the 104 North, crossing the
"Hood Canal Floating Bridge" (my, what a boring name), then finally
joining the 101 North. The environment was quite varied, from bare
plains and hills, to wooded areas, to quite dense forests, then into
inhabited areas - quite a long stretch of human presence, from the
Sequim Bay to Port Angeles.
Port Angeles surprised me: it had nice views of the ocean, and an
interesting port (a few big ships), but it was much smaller than I
expected. The 101 crosses it, and in less than 10 minutes or so it was
already over. I expected something nicer, based on the name, but
Anyway, onwards!
Soon I was at a crossroads and had to decide: I could either follow
the 101, crossing the Elwha River and then to Lake Crescent, then go
north on the 113/112, or go right off 101 onto 112, and follow it
until close to my goal. I took the 112, because on the map it looked
"nicer", and closer to the shore.
Well, the road itself was nice, but quite narrow and twisty here and
there, and there was some annoying traffic, so I didn't enjoy this
segment very much. At least it had the very interesting property (to
me) that whenever I got closer to the ocean, the sun suddenly
disappeared, and I was finding myself in the fog:
So my plan to drive nicely along the coast failed. At one point, there
was even heavy smoke (not fog!), and I wondered for a moment how safe
was to drive out there in the wilderness (there were other cars
though, so I was not alone).
Only quite a bit later, close to Neah Bay, did I finally see the
ocean: I saw a small parking spot, stopped, and crossing a small line
of trees I found myself in a small cove? bay? In any case, I had the
impression I stepped out of the daily life in the city and out into
the far far wilderness:
There was a couple, sitting on chairs, just enjoying the view. I felt
very much intruding, behaving like I did as a tourist: running in,
taking pictures, etc., so I tried at least to be quiet . I then
quickly moved on, since I still had some road ahead of me.
Soon I entered Neah Bay, and was surprised to see once
more blue, and even more blue. I'm a sucker for blue, whether sky blue
or sea blue , so I took a few more pictures (watch out for the evil
fog in the second one):
Well, the town had some event, and there were lots of people, so I
just drove on, now on the last stretch towards the cape. The road here
was also very interesting, yet another environment - I was driving on
Cape Flattery Road, which cuts across the tip of the peninsula (quite
narrow here) along the Waatch River and through its flooding plains
(at least this is how it looked to me). Then it finally starts going
up through the dense forest, until it reaches the parking lot, and
from there, one goes on foot towards the cape. It's a very easy and
nice walk (not a hike), and the sun was shining very nicely through
the trees:
But as I reached the peak of the walk, and started descending towards
the coast, I was surprised, yet again, by fog:
I realised that probably this means the cape is fully in fog, so I
won't have any chance to enjoy the view.
Boy, was I wrong! There are three viewpoints on the cape, and at each
one I was just "wow" and "aah" at the view. Even thought it was not a
sunny summer view, and there was no blue in sight, the combination
between the fog (which was hiding the horizon and even the closer
islands), the angry ocean which was throwing wave after wave at the
shore, making a loud noise, and the fact that even this seemingly
inhospitable area was just teeming with life, was both unexpected and
awesome. I took here waay to many pictures, here are just a couple
inlined:
I spent around half an hour here, just enjoying the rawness of
nature. It was so amazing to see life encroaching on each bit of land,
even though it was not what I would consider a nice place. Ah, how we
see everything through our own eyes!
The walk back was through fog again, and at one point it switched over
back to sunny. Driving back on the same road was quite different,
knowing what lies at its end. On this side, the road had some parking
spots, so I managed to stop and take a picture - even though this area
was much less wild, it still has that outdoors flavour, at least for
me:
Back in Neah Bay, I stopped to eat. I had a place in mind from
TripAdvisor, and indeed - I was able to get a custom order pizza at
"Linda's Woodfired Kitchen". Quite good, and I ate without hurry,
looking at the people walking outside, as they were coming back from
the fair or event that was taking place.
While eating, a somewhat disturbing thought was going through my
mind. It was still early, around two to half past two, so if
I went straight back to Kirkland I would be early at the hotel. But it
was also early enough that I could - in theory at least - still do the
"big round-trip". I was still rummaging the thought as I left
On the drive back I passed once more near
Sekiu, Washington, which is a very small place but the
map tells me it even has an airport! Fun, and the view was quite nice
(a bit of blue before the sea is swallowed by the fog):
After passing Sekiu and Clallam Bay, the 112 curves inland and goes on
a bit until you are at the crossroads: to the left the 112 continues,
back the same way I came; to the right, it's the 113, going south
until it meets the 101. I looked left - remembering the not-so-nice
road back, I looked south - where a very appealing, early afternoon
sun was beckoning - so I said, let's take the long way home!
It's just a short stretch on the 113, and then you're on the 101. The
101 is a very nice road, wide enough, and it goes through very very
nice areas. Here, west to south-west of the Olympic Mountains, it's a
very different atmosphere from the 112/101 that I drove on in the
morning; much warmer colours, a bit different tree types (I think),
and more flat. I soon passed through Forks, which is one of the places
I looked at when searching for hotels. I did so without any knowledge
of the town itself (its wikipedia page is quite drab), so imagine my
surprise when a month later I learned from a colleague that this is
actually a very important place for vampire-book fans. Oh my, and I
didn't even stop! This town also had some event, so I just drove on,
enjoying the (mostly empty) road.
My next planned waypoint was Ruby Beach, and I was
looking forward to relaxing a bit under the warm sun - the drive was
excellent, weather perfect, so I was watching the distance countdown
on my Garmin. At two miles out, the "Near waypoint Ruby Beach" message
appeared, and two seconds later the sun went out. What the I was
hoping this is something temporary, but as I slowly drove the
remaining mile I couldn't believe my eyes that I was, yet again,
finding myself in the fog
I park the car, thinking that asking for a refund would at least allow
me to feel better - but it was I who planned the trip! So I resigned
myself, thinking that possibly this beach is another special location
that is always in the fog. However, getting near the beach it was
clear that it was not so - some people were still in their bathing
suits, just getting dressed, so it seems I was just unlucky with
regards to timing. However, I the beach itself was nice, even in the
fog (I later saw online sunny pictures, and it is quite beautiful),
the the lush trees reach almost to the shore, and the way the rocks
are sitting on the beach:
Since the weather was not that nice, I took a few more pictures, then
headed back and started driving again. I was soo happy that the
weather didn't clear at the 2 mile mark (it was not just Ruby Beach!),
but alas - it cleared as soon as the 101 turns left and leaves the
shore, as it crosses the Queets river. Driving towards my next planned
stop was again a nice drive in the afternoon sun, so I think it simply
was not a sunny day on the Pacific shore. Maybe seas and oceans have
something to do with fog and clouds ! In Switzerland, I'm very happy
when I see fog, since it's a somewhat rare event (and seeing mountains
disappearing in the fog is nice, since it gives the impression of a
wider space). After this day, I was a bit fed up with fog for a while
Along the 101 one reaches Lake Quinault, which seemed
pretty nice on the map, and driving a bit along the lake - a local
symbol, the "World's largest spruce tree". I don't know what a spruce
tree is, but I like trees, so I was planning to go there, weather
allowing. And the weather did cooperate, except that the tree was not
so imposing as I thought! In any case, I was glad to stretch my legs a
bit:
However, the most interesting thing here in Quinault was not this
tree, but rather - the quiet little town and the view on the lake, in
the late afternoon sun:
The entire town was very very quiet, and the sun shining down on the
lake gave an even stronger sense of tranquillity. No wind, not many
noises that tell of human presence, just a few, and an overall sense
of peace. It was quite the opposite of the Cape Flattery and a very
nice way to end the trip.
Well, almost end - I still had a bit of driving ahead. Starting from
Quinault, driving back and entering the 101, driving down to Aberdeen:
then turning east towards Olympia, and back onto the highways.
As to Aberdeen and Olympia, I just drove through, so I couldn't make
any impression of them. The old harbour and the rusted things in
Aberdeen were a bit interesting, but the day was late so I didn't
stop.
And since the day shouldn't end without any surprises, during the last
profile change between walking and driving in Quinault, my GPS decided
to reset its active maps list and I ended up with all maps
activated. This usually is not a problem, at least if you follow a
pre-calculated route, but I did trigger recalculation as I restarted
my driving, so the Montana was trying to decide on which map to route
me - between the Garmin North America map and the Open StreeMap one,
the result was that it never understood which road I was on. It always
said "Drive to I5", even though I was on I5. Anyway, thanks to road
signs, and no thanks to "just this evening ramp closures", I was able
to arrive safely at my hotel.
Overall, a very successful, if long trip: around 725 kilometres,
10h:30m moving, 13h:30m total:
There were many individual good parts, but the overall think about
this road trip was that I was able to experience lots of different
environments of the peninsula on the same day, and that overall it's a
very very nice area.
The downside was that I was in a rush, without being able to actually
stop and enjoy the locations I visited. And there's still so much to
see! A two nights trip sound just about right, with some long hikes in
the rain forest, and afternoons spent on a lake somewhere.
Another not so optimal part was that I only had my "travel" camera (a
Nikon 1 series camera, with a small sensor), which was a bit
overwhelmed here and there by the situation. It was fortunate that the
light was more or less good, but looking back at the pictures, how I
wish that I had my "serious" DSLR
So, that means I have two reasons to go back! Not too soon though,
since Mount Rainier is also a good location to visit .
If the pictures didn't bore you yet, the entire gallery is on
my smugmug site. In
any case, thanks for reading!
A common way to debug a network server is to use 'telnet' or 'nc' to
connect to the server and issue some commands in the protocol to verify
whether everything is working correctly. That obviously only works for
ASCII protocols (as opposed to binary protocols), and it obviously also
only works if you're not using any encryption.
But that doesn't mean you can't test an encrypted protocol in a
similar way, thanks to openssl's s_client:
wouter@country:~$ openssl s_client -host samba.grep.be -port 443
CONNECTED(00000003)
depth=0 /C=BE/ST=Antwerp/L=Mechelen/O=NixSys BVBA/CN=svn.grep.be/emailAddress=wouter@grep.be
verify error:num=18:self signed certificate
verify return:1
depth=0 /C=BE/ST=Antwerp/L=Mechelen/O=NixSys BVBA/CN=svn.grep.be/emailAddress=wouter@grep.be
verify return:1
---
Certificate chain
0 s:/C=BE/ST=Antwerp/L=Mechelen/O=NixSys BVBA/CN=svn.grep.be/emailAddress=wouter@grep.be
i:/C=BE/ST=Antwerp/L=Mechelen/O=NixSys BVBA/CN=svn.grep.be/emailAddress=wouter@grep.be
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDXDCCAsWgAwIBAgIJAITRhiXp+37JMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
BAYTAkJFMRAwDgYDVQQIEwdBbnR3ZXJwMREwDwYDVQQHEwhNZWNoZWxlbjEUMBIG
A1UEChMLTml4U3lzIEJWQkExFDASBgNVBAMTC3N2bi5ncmVwLmJlMR0wGwYJKoZI
hvcNAQkBFg53b3V0ZXJAZ3JlcC5iZTAeFw0wNTA1MjEwOTMwMDFaFw0xNTA1MTkw
OTMwMDFaMH0xCzAJBgNVBAYTAkJFMRAwDgYDVQQIEwdBbnR3ZXJwMREwDwYDVQQH
EwhNZWNoZWxlbjEUMBIGA1UEChMLTml4U3lzIEJWQkExFDASBgNVBAMTC3N2bi5n
cmVwLmJlMR0wGwYJKoZIhvcNAQkBFg53b3V0ZXJAZ3JlcC5iZTCBnzANBgkqhkiG
9w0BAQEFAAOBjQAwgYkCgYEAsGTECq0VXyw09Zcg/OBijP1LALMh9InyU0Ebe2HH
NEQ605mfyjAENG8rKxrjOQyZzD25K5Oh56/F+clMNtKAfs6OuA2NygD1/y4w7Gcq
1kXhsM1MOIOBdtXAFi9s9i5ZATAgmDRIzuKZ6c2YJxJfyVbU+Pthr6L1SFftEdfb
L7MCAwEAAaOB4zCB4DAdBgNVHQ4EFgQUtUK7aapBDaCoSFRWTf1wRauCmdowgbAG
A1UdIwSBqDCBpYAUtUK7aapBDaCoSFRWTf1wRauCmdqhgYGkfzB9MQswCQYDVQQG
EwJCRTEQMA4GA1UECBMHQW50d2VycDERMA8GA1UEBxMITWVjaGVsZW4xFDASBgNV
BAoTC05peFN5cyBCVkJBMRQwEgYDVQQDEwtzdm4uZ3JlcC5iZTEdMBsGCSqGSIb3
DQEJARYOd291dGVyQGdyZXAuYmWCCQCE0YYl6ft+yTAMBgNVHRMEBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4GBADGkLc+CWWbfpBpY2+Pmknsz01CK8P5qCX3XBt4OtZLZ
NYKdrqleYq7r7H8PHJbTTiGOv9L56B84QPGwAzGxw/GzblrqR67iIo8e5reGbvXl
s1TFqKyvoXy9LJoGecMwjznAEulw9cYcFz+VuV5xnYPyJMLWk4Bo9WCVKGuAqVdw
-----END CERTIFICATE-----
subject=/C=BE/ST=Antwerp/L=Mechelen/O=NixSys BVBA/CN=svn.grep.be/emailAddress=wouter@grep.be
issuer=/C=BE/ST=Antwerp/L=Mechelen/O=NixSys BVBA/CN=svn.grep.be/emailAddress=wouter@grep.be
---
No client certificate CA names sent
---
SSL handshake has read 1428 bytes and written 316 bytes
---
New, TLSv1/SSLv3, Cipher is DHE-RSA-AES256-SHA
Server public key is 1024 bit
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1
Cipher : DHE-RSA-AES256-SHA
Session-ID: 65E69139622D06B9D284AEDFBFC1969FE14E826FAD01FB45E51F1020B4CEA42C
Session-ID-ctx:
Master-Key: 606553D558AF15491FEF6FD1A523E16D2E40A8A005A358DF9A756A21FC05DFAF2C9985ABE109DCD29DD5D77BE6BC5C4F
Key-Arg : None
Start Time: 1222001082
Timeout : 300 (sec)
Verify return code: 18 (self signed certificate)
---
HEAD / HTTP/1.1
Host: svn.grep.be
User-Agent: openssl s_client
Connection: close
HTTP/1.1 404 Not Found
Date: Sun, 21 Sep 2008 12:44:55 GMT
Server: Apache/2.2.3 (Debian) mod_auth_kerb/5.3 DAV/2 SVN/1.4.2 PHP/5.2.0-8+etch11 mod_ssl/2.2.3 OpenSSL/0.9.8c
Connection: close
Content-Type: text/html; charset=iso-8859-1
closed
wouter@country:~$
As you can see, we connect to an HTTPS server, get to see what the
server's certificate looks like, issue some commands, and the server
responds properly. It also works for (some) protocols who work in a
STARTTLS kind of way:
wouter@country:~$ openssl s_client -host samba.grep.be -port 587 -starttls smtp
CONNECTED(00000003)
depth=0 /C=BE/ST=Antwerp/L=Mechelen/O=NixSys BVBA/CN=samba.grep.be
verify error:num=18:self signed certificate
verify return:1
depth=0 /C=BE/ST=Antwerp/L=Mechelen/O=NixSys BVBA/CN=samba.grep.be
verify return:1
---
Certificate chain
0 s:/C=BE/ST=Antwerp/L=Mechelen/O=NixSys BVBA/CN=samba.grep.be
i:/C=BE/ST=Antwerp/L=Mechelen/O=NixSys BVBA/CN=samba.grep.be
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDBDCCAm2gAwIBAgIJAK53w+1YhWocMA0GCSqGSIb3DQEBBQUAMGAxCzAJBgNV
BAYTAkJFMRAwDgYDVQQIEwdBbnR3ZXJwMREwDwYDVQQHEwhNZWNoZWxlbjEUMBIG
A1UEChMLTml4U3lzIEJWQkExFjAUBgNVBAMTDXNhbWJhLmdyZXAuYmUwHhcNMDgw
OTIwMTYyMjI3WhcNMDkwOTIwMTYyMjI3WjBgMQswCQYDVQQGEwJCRTEQMA4GA1UE
CBMHQW50d2VycDERMA8GA1UEBxMITWVjaGVsZW4xFDASBgNVBAoTC05peFN5cyBC
VkJBMRYwFAYDVQQDEw1zYW1iYS5ncmVwLmJlMIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQCee+Ibci3atTgoJqUU7cK13oD/E1IV2lKcvdviJBtr4rd1aRWfxcvD
PS00jRXGJ9AAM+EO2iuZv0Z5NFQkcF3Yia0yj6hvjQvlev1OWxaWuvWhRRLV/013
JL8cIrKYrlHqgHow60cgUt7kfSxq9kjkMTWLsGdqlE+Q7eelMN94tQIDAQABo4HF
MIHCMB0GA1UdDgQWBBT9N54b/zoiUNl2GnWYbDf6YeixgTCBkgYDVR0jBIGKMIGH
gBT9N54b/zoiUNl2GnWYbDf6YeixgaFkpGIwYDELMAkGA1UEBhMCQkUxEDAOBgNV
BAgTB0FudHdlcnAxETAPBgNVBAcTCE1lY2hlbGVuMRQwEgYDVQQKEwtOaXhTeXMg
QlZCQTEWMBQGA1UEAxMNc2FtYmEuZ3JlcC5iZYIJAK53w+1YhWocMAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAAnMdbAgLRJ3xWOBlqNjLDzGWAEzOJUHo
5R9ljMFPwt1WdjRy7L96ETdc0AquQsW31AJsDJDf+Ls4zka+++DrVWk4kCOC0FOO
40ar0WUfdOtuusdIFLDfHJgbzp0mBu125VBZ651Db99IX+0BuJLdtb8fz2LOOe8b
eN7obSZTguM=
-----END CERTIFICATE-----
subject=/C=BE/ST=Antwerp/L=Mechelen/O=NixSys BVBA/CN=samba.grep.be
issuer=/C=BE/ST=Antwerp/L=Mechelen/O=NixSys BVBA/CN=samba.grep.be
---
No client certificate CA names sent
---
SSL handshake has read 1707 bytes and written 351 bytes
---
New, TLSv1/SSLv3, Cipher is DHE-RSA-AES256-SHA
Server public key is 1024 bit
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1
Cipher : DHE-RSA-AES256-SHA
Session-ID: 6D28368494A3879054143C7C6B926C9BDCDBA20F1E099BF4BA7E76FCF357FD55
Session-ID-ctx:
Master-Key: B246EA50357EAA6C335B50B67AE8CE41635EBCA6EFF7EFCE082225C4EFF5CFBB2E50C07D8320E0EFCBFABDCDF8A9A851
Key-Arg : None
Start Time: 1222000892
Timeout : 300 (sec)
Verify return code: 18 (self signed certificate)
---
250 HELP
quit
221 samba.grep.be closing connection
closed
wouter@country:~$
OpenSSL here connects to the server, issues a proper EHLO command,
does STARTTLS, and then gives me the same data as it did for the HTTPS
connection.
Isn't that nice.