Recovering EC2 Window Passwords

Recovering a Lost Password on an EC2 Windows Machine

Have you ever ran into an issue that you are unable to recover a Windows password for an EC2 instance regardless of the circumstances? I know I was. In fact, I followed step by step all of the EC2 recovery tutorials, including using EC2config. Unfortunately, none of these worked for me due to, because I Was unaware that the EC2 recovery documentation only worked on instances that are based on a base AMI image – not those copied from other AMI’s (where I ran into my issue). Therefore I was locked out of an instance where I took over administration, with no way in.

Enter, CHNTPW: a tool designed to edit Windows NT registry values from Linux. The following steps allowed me to log into an Administrator account on my Windows EC2 instance without having the initial password. Beware though, steps suggested here are very insecure as they are being operated, and I would highly suggest setting up security groups to avoid any attempts of using RDP (as I used in this tutorial) other than authorized IP addresses.

Also, this tutorial is meant for your own recovery – NOT HACKING someone else’s instance. I hate putting this statement here, but figured with whats going on in the interwebs I want to make that VERY clear.

Lets Hack us a Windows Account

To simplify this tutorial, we are going to set up some default variables.

InstanceID Operating System Description
i-windows Windows Windows Machine that is Locked
i-linux Linux t2-micro machine with chntpw

Step 1: Create t2.micro Linux

  1. Select a linux variant with CHNTPW support. t2.micro is fine.
  2. SCP or download chntpw script into user’s path
  3. keep terminal open — move to step 2

Step 2: Backing up Windows

This step will require downtime for your Windows box (it took me about 10m total, and I took my sweet time, but will vary based on size of EBS)

  1. shutdown i-windows instance (need a clean shutdown from EC2 console)
  2. detatch root volume (vol-windows-root for simplicity)
  3. snapshot vol-windows-root volume

Step 3: Volume Mount

  1. Attach vol-windows-root to i-linux (record mount point)
  2. i-linux terminal > mount -t ntfs-3g /dev/xvdf1 /media/windows
  3. i-linux terminal > cd /media/windows/Windows/System32/config
  4. i-linux terminal > chntpw -u <User> SAM
  5. i-linux terminal > input: 1
  6. i-linux terminal > input: save and exit
  7. i-linux terminal > chntpw -e /mnt/Windows/System32/config/SYSTEM
  8. i-linux terminal > cd \CurrentControlSet\Control\Lsa
    • WARNING: you could have multiple CurrentControlSets. In my case, I did this same step on ALL of them
  9. i-linux terminal > ed LimitBlankPasswordUse
  10. i-linux terminal > input: 0
  11. i-linux terminal > input: save and exit
  12. i-linux terminal > cd ~ ; umount /media/windows

we are now done with linux (hopefully)

Summary: We mounted the Windows Registry, blanked out the ‘s password, and then enabled RDP on blank passwords into the machine. Once again, make sure you have security groups not allowing any anonymous traffic in on the RDP port.

Step 4: re-mount and launch windows

  1. detatch vol-windows-root from i-linux instance
  2. reattach vol-windows-root under /dev/sda1 to i-windows
  3. Boot up i-windows
  4. RDP in as user
  5. Change your Password
  6. Regedit the LSA values to ‘1’ again

Resources

M8s: Moodle in Kubernetes

M8s: Moodle in Kubernetes


WARNING

This documentation is purely for example. Please take into consideration all resource restraints and deployment options (such as logging) before taking this tutorial into production. I will be adding bits and pieces over time as I am more familiar with best practices, but also welcome any comments/suggestions as people walkthrough this tutorial!


Table of Contents:

  1. Getting Started
  2. Proxy Setup
  3. Postgres Setup
  4. Moodle Setup
  5. TLDR;

M8s is a Kubernetes deployment tutorial for Moodle. There is absolutely an intention to move this to a Helm Chart however deploying Moodle to Kubernetes was the priority. In this example I will walk you through setting up an Ingress Proxy, Postgres and finally Moodle in your Kubernetes environment to deploy Moodle.

Note that the Ingress Proxy used here, Traefik, is interchangable with any other method you choose.


Getting Started

Don’t have Kubernetes?: I suggest starting with MiniKube to get going.

First and foremost, to get the files mentioned in this tutorial clone the tutorial repository. As stated before, I plan on moving M8s to a Helm Chart after I get the initial manifests created.


$ git clone https://github.com/jbkc85/moodle-kubernetes-tutorial $ cd moodle-kubernetes-tutorial

Proxy Setup

As mentioned before, we will be using Traefik. To get traefik setup, you can just run the following command:


$ kubectl apply -f m8s/proxy/

This will apply a Kubernetes Ingress Provider as well as a WebUI for Traefik. This is basically taken directly from the Kubernetes Documentation over at Traefik, so I won’t be going too into detail here about it.

To verify we have our proxy setup (taken directly from the documentation in Traefik mentioned earlier), simply run the following:


$ kubectl get pods --namespace=kube-system NAME READY STATUS RESTARTS AGE kube-addon-manager-minikube 1/1 Running 3 29d kubernetes-dashboard-fhz0w 1/1 Running 3 29d tiller-deploy-327544198-dgfaw 1/1 Running 3 29d traefik-ingress-controller-678226159-q50aw 1/1 Running 0 11s

notice the traefik-ingress-controller and you are good


$ curl -XGET $(minikube ip) 404 page not found

we get a 404 because no ingress is currently configured for the minikube ip, and therefore nothing is routed.

Note: The reason we only create a service and ingress in the traefik-webui.yaml is because the traefik.yaml actually starts the WebUI on port 8080 – it just doesn’t expose it outside of the internal network.

Postgres Setup

Postgres is another obvious interchangable part of the m8s setup. However since the database of Moodle is essential, I will be going a bit more in detail on how to set it up.

Persistent Volume

File: m8s/postgres/persistent-volume.yaml

The first step is getting a persistent volume setup in Kubernetes. This is important as if you don’t create a persistent volume, the data from Postgres can be potentially lost.


apiVersion: v1 kind: PersistentVolume metadata: name: local-postgresql-pv labels: type: local spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce hostPath: path: /tmp/postgresql

Lets go ahead and get the Persistent Volume created:


$ kubectl apply -f m8s/postgres/persistent-volume.yaml

Secrets

Passwords in Kubernetes isn’t that difficult if you pass them in through the environment. However, this isn’t secure nor a best practice. Therefore instead of making an ‘easy tutorial’, we are going to go through the creation of secrets. For this particular setup, we use four secrets: Root Password, Database, Username and Password. To create them, we basically upload them to the API through the ‘secrets’ type from plain text files.

If you wish to create your own text files, simply use the following:


# create txt files if you wish $ echo -n "your_value" > m8s/postgres/postgres-rootpassword.txt $ echo -n "your_value" > m8s/postgres/postgres-database.txt $ echo -n "your_value" > m8s/postgres/postgres-user.txt $ echo -n "your_value" > m8s/postgres/postgres-password.txt

If you wish to use the default (everything is moodle), just leave the files as they are.

Whichever you choose, its now time to upload these secrets. Once again, to read more about Secrets just visit the documentation site.


$ kubectl create secret generic postgres-credentials --from-file=m8s/postgres/postgres-rootpassword.txt --from-file=m8s/postgres/postgres-database.txt --from-file=m8s/postgres/postgres-user.txt --from-file=m8s/postgres/postgres-password.txt

After the secret is created, you should be able to describe it based on the name given, in our case postgres-credentials.


$ kubectl describe secret postgres-credentials Name: postgres-credentials Namespace: default Labels: <none> Annotations: <none> Type: Opaque Data ==== postgres-database.txt: 6 bytes postgres-password.txt: 6 bytes postgres-rootpassword.txt: 6 bytes postgres-user.txt: 6 bytes

Now we have our secrets, and we can get onto the Deployment!


Deployment

File: m8s/postgres/deployment.yaml

The deployment of Postgres in Kubernetes requires a few pieces to operate as we want it to. Though they all are organized in the same YAML file, I will show each one in detail here.

First, we have our a service. The service allows for communication to the pods under the selector metadata. To learn more about services in Kubernetes, simply read the docs for services.

Please note that this port is exposed only internally to the pods in the namespace we created.


apiVersion: v1 kind: Service metadata: name: moodle-postgresql labels: app: moodle spec: ports: - port: 5432 selector: app: moodle tier: postgresql clusterIP: None

Next, we have to claim our Persistent Volume, which you created in the first step. In some cases, a Persistent Volume can only have a certain amount of claims. So, in this particular example we are making our claim!

Once again, to read more about Persistent Volumes, go to the Kubernetes Docs!


apiVersion: v1 kind: PersistentVolumeClaim metadata: name: postgresql-claim labels: app: moodle spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi

Finally, we get to the deployment.


apiVersion: extensions/v1beta1 kind: Deployment metadata: name: moodle-postgresql labels: app: moodle spec: strategy: type: Recreate template: metadata: labels: app: moodle tier: postgresql spec: containers: - image: postgres:9.5-alpine name: database env: - name: ROOT_PASSWORD valueFrom: secretKeyRef: name: postgres-credentials key: postgres-rootpassword.txt - name: DATABASE valueFrom: secretKeyRef: name: postgres-credentials key: postgres-database.txt - name: USER valueFrom: secretKeyRef: name: postgres-credentials key: postgres-username.txt - name: PASSWORD valueFrom: secretKeyRef: name: postgres-credentials key: postgres-password.txt ports: - containerPort: 5432 name: postgresql volumeMounts: - name: postgresql-persistent-storage mountPath: /var/lib/postgresql/ volumes: - name: postgresql-persistent-storage persistentVolumeClaim: claimName: postgresql-claim

To deploy, simply use the same kubectl apply command we have been using:


$ kubectl apply -f m8s/postgres/deployment.yaml

Moodle

Now what we have all been waiting for, the deployment of Moodle. In this example, Moodle will be utilizing the following resources in Kubernetes:

  • Persistent Volume: Once again, the Persistent Volume is used to ensure our ‘moodledata’ is not erased on accident when/if this group of pods are destroyed. In production deployments, I would highly suggest looking into alternative methods other than Host-based Mounting, but as this is an example it is what I am using. skip to PersistentVolumes
  • Service: As mentioned before Services expose underlying pods in a given namespace. skip to Services
  • Ingress: An ingress is an instruction to inform Kubernetes (also Traefik in our tutorial) on how to route incoming traffic. skip to Ingress
  • ConfigMap: ConfigMaps are as they sound, a method of storing configurations in Kubernetes. Please remember to read about them and security implications before using them in Production! skip to configmap
  • Deployment: Providing metadata to spin up pods and replica sets in Kubernetes. skip to Deployment

Persistent Volume

Again, creating the persitent-volume is pretty straight forward.


apiVersion: v1 kind: PersistentVolume metadata: name: local-moodledata-pv labels: type: local spec: capacity: storage: 2Gi accessModes: - ReadWriteMany hostPath: path: /tmp/moodledata

Because we are using a persistent volume on the hostPath, we are going to have to create it manually and adjust some permissions to ensure its readable/writable by www-data, our Moodle user.


$ minikube ssh mkdir /tmp/moodledata $ minikube ssh sudo chown 33:33 /tmp/moodledata $ kubectl apply -f m8s/moodle/persistent-volume.yaml

Check our work:


$ kubectl describe pv local-moodledata-pv Name: local-moodledata-pv Labels: type=local Status: Available Claim: Reclaim Policy: Retain Access Modes: RWO Capacity: 2Gi Message: Source: Type: HostPath (bare host directory volume) Path: /tmp/moodledata

Service and Ingress

Now we want to create a service and Ingress for the underlying Moodle deployment. Note we are doing this first before any deployment is created.

Service


apiVersion: v1 kind: Service metadata: name: moodle labels: app: moodle spec: ports: - port: 80 targetPort: 80 selector: app: moodle tier: frontend

Note: If you want to use SSL, you would still only expose port 80 on this device. Port 443 would be exposed on the proxy which would be responsible for all SSL transactions while the backend can still simply listen on 80.

Ingress

For this ingress, we can use the following host map to access Moodle once brought up in the cluster:

$(minikube ip) : http://moodle.local

This is due to the fact our rules in the Ingress map to ‘moodle.local’, which then will map to oour backend service (under the backend serviceName metadata).


apiVersion: extensions/v1beta1 kind: Ingress metadata: name: moodle-ingress spec: rules: - host: moodle.local http: paths: - backend: serviceName: moodle servicePort: 80

$ kubectl apply -f m8s/moodle/service.yaml service "moodle" created $ kubectl apply -f m8s/moodle/ingress.yaml ingress "moodle-ingress" created

Check our work:


$ kubectl describe svc moodle Name: moodle Namespace: default Labels: app=moodle Selector: app=moodle,tier=frontend Type: ClusterIP IP: 10.0.0.64 Port: <unset> 80/TCP Endpoints: <none> Session Affinity: None $ kubectl describe ingress moodle-ingress Name: moodle-ingress Namespace: default Address: Default backend: default-http-backend:80 (<none>) Rules: Host Path Backends ---- ---- -------- moodle.local moodle:80 (<none>) Annotations: No events.

ConfigMap

As mentioned at the top, using configMap isn’t for everyone. This can certainly be done a bit more securely using an ExtraDopeBadge tool like Kelsey Hightower’s Konfd or the likes, so please keep that in mind as we work through this tutorial.

Basically, I am going to take our config.php that we use with Moodle and pump it into configMap. If you make changes to any of the above persistent volumes or ingress settings, you will need to make changes in the file used in this configMap.


$ kubectl create configmap moodle-site-config --from-file=m8s/moodle/moodle-config.php configmap "moodle-site-config" created

Check our work:


$ kubectl get configmaps moodle-site-config -o yaml apiVersion: v1 data: moodle-config.php: | <?php // Moodle configuration file unset($CFG); global $CFG; $CFG = new stdClass(); $CFG->dbtype = 'pgsql'; $CFG->dblibrary = 'native'; $CFG->dbhost = 'moodle-postgresql'; $CFG->dbname = 'moodle'; $CFG->dbuser = 'moodle'; $CFG->dbpass = 'moodle'; $CFG->prefix = 'mdl'; $CFG->dboptions = array ( 'dbpersist' => 0, ); $CFG->wwwroot = 'http://moodle.local'; $CFG->dataroot = '/moodle/data'; $CFG->admin = 'admin'; $CFG->directorypermissions = 02775; $CFG->passwordsaltmain = 'y0uR34l!ySh0uldtU$3-th1sS&lt'; require_once "/var/www/html/lib/setup.php"; // There is no php closing tag in this file, // it is intentional because it prevents trailing whitespace problems! kind: ConfigMap metadata: creationTimestamp: 2017-01-13T15:36:38Z name: moodle-site-config namespace: default resourceVersion: "6087" selfLink: /api/v1/namespaces/default/configmaps/moodle-site-config uid: 12db3f35-d9a6-11e6-9f63-4217ea3347ce

Great, we should be good to go for the deployment!

Deployment

The deployment is identical to the Postgres deployment in many ways – using the PersistentVolumeClaim and basic Deployment kubernetes objects.


apiVersion: v1 kind: PersistentVolumeClaim metadata: name: moodledata-claim labels: app: moodle spec: accessModes: - ReadWriteMany resources: requests: storage: 2Gi --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: moodle labels: app: moodle spec: strategy: type: Recreate template: metadata: labels: app: moodle tier: frontend annotations: pod.alpha.kubernetes.io/init-containers: '[ { "name": "moodle-init", "image": "alpine:3.5", "imagePullPolicy": "IfNotPresent", "command": ["sh", "-c", "chown -R 33:33 /moodledata", ";", "chmod 2775 /moodledata"], "volumeMounts": [ { "name": "moodledata", "mountPath": "/moodledata" } ] } ]' spec: containers: - image: jbkc85/docker-moodle name: moodle ports: - containerPort: 80 name: moodle resources: requests: cpu: 300m memory: 128Mi volumeMounts: - name: moodledata mountPath: /moodle/data - name: config mountPath: /moodle/conf volumes: - name: moodledata persistentVolumeClaim: claimName: moodledata-claim - name: config configMap: name: moodle-site-config items: - key: moodle-config.php path: config.php

Lets fire her up!


$ kubectl apply -f m8s/moodle/deployment.yaml persistentvolumeclaim "moodledata-claim" created deployment "moodle" created

Check our work:


$ kubectl describe deployment moodle Name: moodle Namespace: default CreationTimestamp: Fri, 13 Jan 2017 09:54:39 -0600 Labels: app=moodle Selector: app=moodle,tier=frontend Replicas: 1 updated | 1 total | 0 available | 1 unavailable StrategyType: Recreate MinReadySeconds: 0 OldReplicaSets: <none> NewReplicaSet: moodle-476540258 (1/1 replicas created) Events: FirstSeen LastSeen Count From SubobjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 19s 19s 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set moodle-476540258 to 1

TLDR


$ git clone https://github.com/jbkc85/moodle-kubernetes-tutorial $ cd moodle-kubernetes-tutorial $ kubectl apply -f m8s/proxy/ service "traefik-web-ui" created ingress "traefik-web-ui" created deployment "traefik-ingress-controller" created $ kubectl create secret generic postgres-credentials --from-file=m8s/postgres/postgres-rootpassword.txt --from-file=m8s/postgres/postgres-database.txt --from-file=m8s/postgres/postgres-user.txt --from-file=m8s/postgres/postgres-password.txt secret "postgres-credentials" created $ kubectl apply -f m8s/postgres/ service "moodle-postgresql" created persistentvolumeclaim "postgresql-claim" created deployment "moodle-postgresql" created persistentvolume "local-postgresql-pv" created $ minikube ssh mkdir /tmp/moodledata $ minikube ssh sudo chown 33:33 /tmp/moodledata $ kubectl create configmap moodle-site-config --from-file=m8s/moodle/moodle-config.php configmap "moodle-site-config" created $ kubectl apply -f m8s/moodle persistentvolumeclaim "moodledata-claim" created deployment "moodle" created ingress "moodle-ingress" created persistentvolume "local-moodledata-pv" created service "moodle" created

Ending Notes

There are many things I need to do to sure up this documentation, but I wanted to share it in case anyone else is looking into it and may have a better solution or deployment already written.

Rather than post comments here, I would highly encourage anyone interested to use Github so I don’t have to worry about ReCaptcha or spam.

Click here for Comments/Suggestions/Issues

Optimizing the Image: Removing Package Overhead

Package Overhead

Part of the Optimizing The Image Series

Special thanks to @opgoodness for Alpine knowledge and reviewing!

Package Overhead is a potential issue some may run into while optimizing an image for containers. The area of confusion begins with the base image like Ubuntu or Debian. As they are the most similar to VM’s and easy to compile/build on, many take this as an opportunity to simply extend the ubuntu or debian official repositories for their projects. Unfortunately, this does not always lead to an optimized image and I will provide a few points in why you should attempt to compile what is needed for your application and only that, avoiding mistakes I have made in the past.

If you aren’t familiar with Alpine, a minimal security-oriented Linux Distro, I would highly suggest reading about it – mainly because we are going to use it.

NodeJS: Containerize THIS

Lets take a NodeJS application for example. Lets break this down into steps:

  1. we need nodejs, better install it
  2. we need npm to install all of our packages
  3. do we bundle js? might need to run that (webpack)

Okay, three steps. Seems pretty simple right? Lets do this in Ubuntu.


FROM ubuntu RUN apt-get update && \ apt-get install -y nodejs npm WORKDIR /app CMD ["node"]

At first when i built this image I only thought ‘wow, this is easy’ – until I saw the image size.


ubuntu-node latest 878f2533f01a Less than a second ago 463.5 MB

The first thought that came to me: What exactly did I just install? I thought containers were tiny and to the point…which well designed ones are. But, in using the base image of Ubuntu I was not considering the overhead of the base image. Sure, apt-get is awesome and easy – but have you ever considered what all that installs?

Lets walk through this…

The Typical Install


root@d9506c4670ed:/# apt-get install nodejs Reading package lists... Done Building dependency tree Reading state information... Done The following additional packages will be installed: libicu55 libssl1.0.0 libuv1 The following NEW packages will be installed: libicu55 libssl1.0.0 libuv1 nodejs 0 upgraded, 4 newly installed, 0 to remove and 7 not upgraded. Need to get 11.9 MB of archives. After this operation, 47.7 MB of additional disk space will be used.

Do you ever wonder packages the nodejs relies on? What is in the dependency tree?


root@d9506c4670ed:/# apt-cache depends nodejs nodejs Depends: libc6 Depends: libgcc1 Depends: libicu55 Depends: libssl1.0.0 Depends: libstdc++6 Depends: libuv1 Depends: zlib1g

This doesn’t seem like much, but we also need to remember NPM:


root@d9506c4670ed:/# apt-get install npm Reading package lists... Done Building dependency tree Reading state information... Done The following additional packages will be installed: binutils build-essential bzip2 ca-certificates cpp cpp-5 dpkg-dev fakeroot file g++ g++-5 gcc gcc-5 gyp ifupdown iproute2 isc-dhcp-client isc-dhcp-common javascript-common libalgorithm-diff-perl libalgorithm-diff-xs-perl libalgorithm-merge-perl libasan2 libatm1 libatomic1 libc-dev-bin libc6 libc6-dev libcc1-0 libcilkrts5 libdns-export162 libdpkg-perl libexpat1 libfakeroot libffi6 libfile-fcntllock-perl libgcc-5-dev libgdbm3 libgmp10 libgomp1 libisc-export160 libisl15 libitm1 libjs-inherits libjs-jquery libjs-node-uuid libjs-underscore liblsan0 libmagic1 libmnl0 libmpc3 libmpfr4 libmpx0 libperl5.22 libpython-stdlib libpython2.7-minimal libpython2.7-stdlib libquadmath0 libsqlite3-0 libssl-dev libssl-doc libstdc++-5-dev libtsan0 libubsan0 libuv1-dev libxtables11 linux-libc-dev make manpages manpages-dev mime-support netbase node-abbrev node-ansi node-ansi-color-table node-archy node-async node-block-stream node-combined-stream node-cookie-jar node-delayed-stream node-forever-agent node-form-data node-fstream node-fstream-ignore node-github-url-from-git node-glob node-graceful-fs node-gyp node-inherits node-ini node-json-stringify-safe node-lockfile node-lru-cache node-mime node-minimatch node-mkdirp node-mute-stream node-node-uuid node-nopt node-normalize-package-data node-npmlog node-once node-osenv node-qs node-read node-read-package-json node-request node-retry node-rimraf node-semver node-sha node-sigmund node-slide node-tar node-tunnel-agent node-underscore node-which nodejs-dev openssl patch perl perl-modules-5.22 python python-minimal python-pkg-resources python2.7 python2.7-minimal rename xz-utils zlib1g-dev Suggested packages: binutils-doc bzip2-doc cpp-doc gcc-5-locales debian-keyring g++-multilib g++-5-multilib gcc-5-doc libstdc++6-5-dbg gcc-multilib autoconf automake libtool flex bison gdb gcc-doc gcc-5-multilib libgcc1-dbg libgomp1-dbg libitm1-dbg libatomic1-dbg libasan2-dbg liblsan0-dbg libtsan0-dbg libubsan0-dbg libcilkrts5-dbg libmpx0-dbg libquadmath0-dbg ppp rdnssd iproute2-doc resolvconf avahi-autoipd isc-dhcp-client-ddns apparmor apache2 | lighttpd | httpd glibc-doc libstdc++-5-doc make-doc man-browser node-hawk node-aws-sign node-oauth-sign node-http-signature debhelper ed diffutils-doc perl-doc libterm-readline-gnu-perl | libterm-readline-perl-perl python-doc python-tk python-setuptools python2.7-doc binfmt-support The following NEW packages will be installed: binutils build-essential bzip2 ca-certificates cpp cpp-5 dpkg-dev fakeroot file g++ g++-5 gcc gcc-5 gyp ifupdown iproute2 isc-dhcp-client isc-dhcp-common javascript-common libalgorithm-diff-perl libalgorithm-diff-xs-perl libalgorithm-merge-perl libasan2 libatm1 libatomic1 libc-dev-bin libc6-dev libcc1-0 libcilkrts5 libdns-export162 libdpkg-perl libexpat1 libfakeroot libffi6 libfile-fcntllock-perl libgcc-5-dev libgdbm3 libgmp10 libgomp1 libisc-export160 libisl15 libitm1 libjs-inherits libjs-jquery libjs-node-uuid libjs-underscore liblsan0 libmagic1 libmnl0 libmpc3 libmpfr4 libmpx0 libperl5.22 libpython-stdlib libpython2.7-minimal libpython2.7-stdlib libquadmath0 libsqlite3-0 libssl-dev libssl-doc libstdc++-5-dev libtsan0 libubsan0 libuv1-dev libxtables11 linux-libc-dev make manpages manpages-dev mime-support netbase node-abbrev node-ansi node-ansi-color-table node-archy node-async node-block-stream node-combined-stream node-cookie-jar node-delayed-stream node-forever-agent node-form-data node-fstream node-fstream-ignore node-github-url-from-git node-glob node-graceful-fs node-gyp node-inherits node-ini node-json-stringify-safe node-lockfile node-lru-cache node-mime node-minimatch node-mkdirp node-mute-stream node-node-uuid node-nopt node-normalize-package-data node-npmlog node-once node-osenv node-qs node-read node-read-package-json node-request node-retry node-rimraf node-semver node-sha node-sigmund node-slide node-tar node-tunnel-agent node-underscore node-which nodejs-dev npm openssl patch perl perl-modules-5.22 python python-minimal python-pkg-resources python2.7 python2.7-minimal rename xz-utils zlib1g-dev The following packages will be upgraded: libc6 1 upgraded, 131 newly installed, 0 to remove and 6 not upgraded. Need to get 61.5 MB of archives. After this operation, 243 MB of additional disk space will be used.

And here are the package dependencies:


root@d9506c4670ed:/# apt-cache depends npm npm Depends: nodejs Depends: node-abbrev Depends: node-ansi Depends: node-ansi-color-table Depends: node-archy Depends: node-block-stream Depends: node-fstream Depends: node-fstream-ignore Depends: node-github-url-from-git Depends: node-glob Depends: node-graceful-fs Depends: node-inherits Depends: node-ini Depends: node-lockfile Depends: node-lru-cache Depends: node-minimatch Depends: node-mkdirp Depends: node-gyp Depends: node-nopt Depends: node-npmlog Depends: node-once Depends: node-osenv Depends: node-read Depends: node-read-package-json Depends: node-request Depends: node-retry Depends: node-rimraf Depends: node-semver Depends: node-sha Depends: node-slide Depends: node-tar Depends: node-underscore Depends: node-which

I am going to be honest here – I don’t know what the hell half of these packages are. Are they even necessary? We are at 243MB of packages – but I was also installing recommends, so we can also run --no-install-recommends to narrow down the install:


root@d9506c4670ed:/# apt-get install --no-install-recommends npm Reading package lists... Done Building dependency tree Reading state information... Done The following additional packages will be installed: ca-certificates gyp libc-dev-bin libc6 libc6-dev libexpat1 libffi6 libjs-inherits libjs-node-uuid libjs-underscore libpython-stdlib libpython2.7-minimal libpython2.7-stdlib libsqlite3-0 libssl-dev libuv1-dev linux-libc-dev mime-support node-abbrev node-ansi node-ansi-color-table node-archy node-async node-block-stream node-combined-stream node-cookie-jar node-delayed-stream node-forever-agent node-form-data node-fstream node-fstream-ignore node-github-url-from-git node-glob node-graceful-fs node-gyp node-inherits node-ini node-json-stringify-safe node-lockfile node-lru-cache node-mime node-minimatch node-mkdirp node-mute-stream node-node-uuid node-nopt node-normalize-package-data node-npmlog node-once node-osenv node-qs node-read node-read-package-json node-request node-retry node-rimraf node-semver node-sha node-sigmund node-slide node-tar node-tunnel-agent node-underscore node-which nodejs-dev openssl python python-minimal python-pkg-resources python2.7 python2.7-minimal zlib1g-dev Suggested packages: glibc-doc manpages-dev javascript-common node-hawk node-aws-sign node-oauth-sign node-http-signature debhelper python-doc python-tk python-setuptools python2.7-doc binutils binfmt-support Recommended packages: manpages manpages-dev javascript-common libjs-jquery libssl-doc file build-essential The following NEW packages will be installed: ca-certificates gyp libc-dev-bin libc6-dev libexpat1 libffi6 libjs-inherits libjs-node-uuid libjs-underscore libpython-stdlib libpython2.7-minimal libpython2.7-stdlib libsqlite3-0 libssl-dev libuv1-dev linux-libc-dev mime-support node-abbrev node-ansi node-ansi-color-table node-archy node-async node-block-stream node-combined-stream node-cookie-jar node-delayed-stream node-forever-agent node-form-data node-fstream node-fstream-ignore node-github-url-from-git node-glob node-graceful-fs node-gyp node-inherits node-ini node-json-stringify-safe node-lockfile node-lru-cache node-mime node-minimatch node-mkdirp node-mute-stream node-node-uuid node-nopt node-normalize-package-data node-npmlog node-once node-osenv node-qs node-read node-read-package-json node-request node-retry node-rimraf node-semver node-sha node-sigmund node-slide node-tar node-tunnel-agent node-underscore node-which nodejs-dev npm openssl python python-minimal python-pkg-resources python2.7 python2.7-minimal zlib1g-dev The following packages will be upgraded: libc6 1 upgraded, 72 newly installed, 0 to remove and 6 not upgraded. Need to get 14.9 MB of archives. After this operation, 62.2 MB of additional disk space will be used.

Great, we are down to 62.2MB. About a 75% reduction in installation space from 243MB.

So lets try this again:


FROM ubuntu RUN apt-get update && \ apt-get install --no-install-recommends -y nodejs npm WORKDIR /app CMD ["node"]


ubuntu-node previous 878f2533f01a Less than a second ago 463.5 MB ubuntu-node optimized 0c4d0d886afd About a minute ago 292.5 MB

But is this enough? Our image is still 295.5MB at the end of the day (about 64% of its original size). It is still strange to me that a container is this large? When I think of a container I think of a small portable piece of software – not a VM emulation.

The Minimal Install

Rather than looking through the specs to find the minimal install, Docker is nice enough to provide us with a pretty solid implementation of NodeJS through their ‘official repository’: Official Repository for NodeJS.

docker pull node:7

There, we are done right? We can breathe a sigh of relief and say ‘we optimized our container deployment!!’. Well, we are getting there, but there is still quite a bit of bloat going on in the latest image (v7 at the time of this writting). Lets take a look at their Dockerfile:


FROM buildpack-deps:jessie RUN groupadd --gid 1000 node \ && useradd --uid 1000 --gid node --shell /bin/bash --create-home node # gpg keys listed at https://github.com/nodejs/node RUN set -ex \ && for key in \ 9554F04D7259F04124DE6B476D5A82AC7E37093B \ 94AE36675C464D64BAFA68DD7434390BDBE9B9C5 \ 0034A06D9D9B0064CE8ADF6BF1747F4AD2306D93 \ FD3A5288F042B6850C66B31F09FE44734EB7990E \ 71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 \ DD8F2338BAE7501E3DD5AC78C273792F7D83545D \ B9AE9905FFD7803F25714661B63B535A4C206CA9 \ C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \ ; do \ gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \ done ENV NPM_CONFIG_LOGLEVEL info ENV NODE_VERSION 7.2.1 RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \ && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \ && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \ && grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \ && tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \ && rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \ && ln -s /usr/local/bin/node /usr/local/bin/nodejs CMD [ "node" ]

$ docker pull node:7 $ docker images | grep node node 7 36dc1bb7a52b 3 days ago 655.5 MB

Notice how they build NodeJS from a build script? This is a beautiful example of how container images can be built. However, there is one tiny – potentially ‘nitpicky’ – issue. We are still using Debian! To some this might not matter and it truly might not, but after reading and interacting with Docker I can’t help but wonder why anyone would need Debian or regular VM distro as a container image (and its huge!). Remember, we aren’t implementing a server rather deploying an application/process. This means smaller and singular focused.

Enter Alpine

Instead of doing docker pull node, we are going to specify a specific tag in their repo which uses Alpine.



$ docker pull node:7-alpine $ docker images | grep node node 7-alpine a1c188c2c5e1 3 days ago 55.29 MB node 7 36dc1bb7a52b 3 days ago 655.5 MB

55.29MB. Less than a fourth of the ubuntu container, and much smaller than the original node:7 container built on Debian.

Alpine Drawbacks
  • No BASH by default
  • Different Compiler than main stream Linux (doesnt use glibc)

Final Thoughts

At the end of the day to many people performance is what matters for most. Obviously you need to test your application to ensure it performs as expected on whichever Base Image you decide to build your application ontop of – but don’t forget the lesson: Do you really need that massive base image? The overhead and potential security risks from additional packages may not be worth it.

Never shy away from asking the question Is this container built for my specific use-case and application? Originally I didn’t bother asking these questions. I used phusion/baseimage and rolled with it. In the early days it was about moving to Docker, not necessarily about moving to optimized image installs. Afterall, “we were moving from FreeBSD Jails” I always said. I know now that was an oversight. Why? Because containers are better as application based, small and single process oriented – remove the cruft, secure the image.

Also, please note that removing packages and measuring an image based on disk space is not the only measurement one can make for an optimized image. Next we will take a look at measuring resource utilization of particular packages and how we can potentially optimize from the perspective of CPU/MEM utilization.

Managing WordPress from CLI

As a System Administrator, I prefer things to be super simple and preferably from a command line (CLI).  After working with WordPress enough, I became extremely dissatisfied with the update process and the fact I would have to go somewhere and curl/wget a file down, put it in place, manually remove the old one, and go through the tedious task of updating.  If this was Docker, I would simply re-build an image…but even then, I have to download and update plugins – honestly, its nasty and a waste of time.  This is where WP-CLI comes into play.  WP-CLI is a command line tool (packaged with .phar) that I would recommend, and argue is essential to administrating WordPress.  Here is an example of the code I ran to completely update my WordPress from some ancient version:

$ wp --path=/path/to/jbkc85.com core update
Updating to version 4.4 (en_US)...
Downloading update from https://downloads.wordpress.org/release/wordpress-4.4-new-bundled.zip...
Unpacking the update...
Success: WordPress updated successfully.
$ wp --path=/path/to/jbkc85.com plugin update --all
...
Disabling Maintenance mode...
Success: Translations updates are not needed for the 'English (US)' locale.
Success: Updated 3/3 plugins.
$ wp --path=/path/to/jbkc85.com theme update --all
Enabling Maintenance mode...
...
Disabling Maintenance mode...
Success: Translations updates are not needed for the 'English (US)' locale.
Success: Updated 7/7 themes.

Notice how I had three commands, and it updated multiple areas of my site?  This is what System Administration is all about…find tools to make the job simple so you can work on other important matters.  With this tool not only am I able to keep my sites up-to-date, but I am also able to make a rather interesting Docker distribution for WordPress and building images with brand-new and up-to-date plugins/themes each build (more on this later).  I highly encourage ANYONE running a WordPress site to check this out and start using it!

Resource: http://wp-cli.org/

OwnCloud Password Change

Need to change the admin password of your owncloud install in the MySQL? It is actually quite easy.  Here are the steps I took to get it working:

  1. Log into the server hosting your owncloud installation
  2. Navigate to the sites directory (ie: /var/www/owncloud)
  3. Navigate to the ‘phppass’ 3rd party app (ie: /3rdparty/phpass)
  4. edit the test.php file and replace the following:
    • edit $t_hasher = new PasswordHash(8, FALSE); to –
       $t_hasher = new PasswordHash(8, CRYPT_BLOWFISH!=1);
    • edit $correct = ‘test12345’; to –
       $correct = 'yournewpass'.'yourconfigpasswordsalt';
  5. Run ‘php test.php’
  6. Copy the first line that has ‘Hash: $2a…’
  7. Log into your database (MySQL is the example)
    • mysql -u root -p
    • use owncloud;
    • select * from oc_user;
      • Note the password value, copy it just in case
    • update oc_user set password= ‘generatedhash’ where uid = ‘youruser’;

You should be good to go!

If you are having difficulty finding your ‘yourconfigpasswordsalt’, look in your config file from OwnCloud. In every OwnCloud setup you have a Password Salt configured in your config/config.php file, and that is what you need to use to get an accurate encryption value for your newly generated password.