<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     xmlns:wfw="http://wellformedweb.org/CommentAPI/"
     >
  <channel>
    <title>funcptr</title>
    <link>http://funcptr.net/</link>
    <description>An engineers technical notebook</description>
    <pubDate>Mon, 24 Dec 2018 05:32:02 GMT</pubDate>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Change one password</title>
      <link>http://funcptr.net/2018/12/24/change-one-password</link>
      <pubDate>Mon, 24 Dec 2018 06:19:52 MST</pubDate>
      <category><![CDATA[security]]></category>
      <category><![CDATA[passwords]]></category>
      <guid isPermaLink="true">http://funcptr.net/2018/12/24/change-one-password</guid>
      <description>Change one password</description>
      <content:encoded><![CDATA[<p>It is that time of year where security professionals the world over end up
talking with friends and family about security. It will be inevitable, almost
as inevitable as someone wearing a stupid Christmas sweater they are a little
too proud of.</p>
<p>The standard advice we've been giving for years is pretty simple:</p>
<ol>
<li>Don't re-use your passwords across sites</li>
<li>Use a password manager</li>
</ol>
<p>Anyone that has done technical support for anyone that isn't as familiar with
IT knows well that as soon as you complicate something, you end up getting
twice the calls, even for things that are not your fault; "Well, since you
setup that password thing my printer won't print" ...</p>
<p>It is fantastic advice, it is where we should all strive to be, we should all
have password managers and should never re-use passwords.</p>
<p>However let's change one single password. Start small.</p>
<p>There is likely to be a single account that is the root of trust for all other
accounts. An email address, either at an ISP somewhere (and maybe this is the
year you get them to switch from that old <a href="https://www.earthlink.net/">Earthlink</a> email address?) or
more likely a free email provider.</p>
<p>That's the account we want to target.</p>
<p>If we can secure the root of trust, the email address that can be used for
password reset emails and for phishing we've already won a large battle.
Individual accounts may still be "vulnerable", but now we've closed one giant
hole.</p>
<p>After all, we all learn to walk before we learn how to run. This small step can
set the tone for even more and better security later.</p>
<p>Should we go further? Absolutely, identify the primary accounts that are high
risk, as an example:</p>
<ol>
<li><a href="https://www.facebook.com">Facebook</a></li>
<li><a href="https://www.icloud.com">Apple iCloud</a></li>
<li><a href="https://account.microsoft.com/">Microsoft account</a></li>
<li><a href="https://twitter.com">Twitter</a></li>
</ol>
<p>Facebook Login/Twitter is used across many different websites, Apple's iCloud
allows remote wipe of devices, and Microsoft Account is used for access to
local machines and likely to OneDrive and other online accounts storing
personal documents and files.</p>
<p>There are many more that I am missing, those can be next, but even the above
tend to roll back up to a single email address.</p>
<p>There is nothing new under the sun, and password re-use is well known and
ridiculed, even Randall Munroe of <a href="https://xkcd.com/">XKCD</a> fame published a comic about
<a href="https://xkcd.com/792/">password re-use</a> a long time ago, however there is one comic that comes to
mind to help create better passwords:</p>
<p><a href="https://xkcd.com/936/">correct horse battery staple</a></p>
<p>Pick four random words from the English language, create a funny sentence and
you are off to the races. Don't use <code>correct horse battery staple</code> as a
password, it's a terrible password now, but the idea behind generating such a
password is fantastic.</p>
<p>Just changing one password can increase someones security posture just a little
bit, and who knows, next year you'll have received less spam email that can be
traced back to their address book being siphoned off and then abused.</p>
<p>For bonus points, have them sign up for <a href="https://haveibeenpwned.com/">';--have i been pwned?</a>, now each
time a new service is breached your friends or relatives will get a little bit
of notice, and can get an idea for why different passwords are a necessity
these days, and maybe next year they will ask you to show them how to set up
that password manager so they can be even more secure!</p>
<p>Happy Holidays, and good luck with your IT help desk duties this year,
especially getting that printer driver installed, because lets be honest, we'll
get blamed for the broken printer in two months whether we touched it or not.</p>]]></content:encoded>
    </item>
    <item>
      <title>Python Packaging and Distribution</title>
      <link>http://funcptr.net/2018/05/21/python-packaging-and-distribution</link>
      <pubDate>Mon, 21 May 2018 14:00:00 MDT</pubDate>
      <category><![CDATA[python]]></category>
      <category><![CDATA[packaging]]></category>
      <category><![CDATA[open source]]></category>
      <guid isPermaLink="true">http://funcptr.net/2018/05/21/python-packaging-and-distribution</guid>
      <description>Python Packaging and Distribution</description>
      <content:encoded><![CDATA[<p>There's been many a discussion online related to a variety of tools related to
Python packaging and distribution. There is <a href="https://pip.pypa.io">pip</a>, <a href="https://docs.pipenv.org">pipenv</a> and
<a href="https://poetry.eustace.io">poetry</a> that have been the tools under discussion.</p>
<p>As an open source maintainer as part of the <a href="https://pylonsproject.org">Pylons Project</a>, while I would
love to be writing code I end up spending a lot of time dealing with user
questions around packaging/distributing their source code using the software
I've helped build, and as we move forward myself and other maintainers were
wondering if we were actually helping users move forward in the best way
possible using best of breed tools.<sup id="fnref:201805tooldiscussionpyramid"><a class="footnote-ref" href="#fn:201805tooldiscussionpyramid" rel="footnote">1</a></sup></p>
<p>As the Python community has moved from <a href="https://setuptools.readthedocs.io/en/latest/easy_install.html#easy-install">easy_install</a> to pip, we too have
kept the documentation up to date. We went from <code>python setup.py develop</code> to
<code>pip install -e .</code> to create editable installs of local projects, and try to
let people know the pitfalls of using both easy_install and pip in the same
project (mostly with an answer that falls in line with: remove your virtual
environment and start over, just use pip).</p>
<p>As part of <a href="https://trypyramid.com">Pyramid</a> we have developed and maintain various different
<a href="https://cookiecutter.readthedocs.io/en/latest/">cookiecutter</a> templates, and our goal is to attempt to provide templates
that are both useful, but also follow best practices that are being adopted
within the community at large so that newcomers can use their existing
skills/knowledge and those that are starting with us walk away with a knowledge
and experience that applies not just to development of Pyramid applications,
but also applies to the broader community as a whole.</p>
<h1>pip</h1>
<p>Pip is a great tool that has simplified installation of packages, it supports
using binary distributions named <a href="https://github.com/pypa/wheel">wheels</a> and has a way to easily install
software from the <a href="https://pypi.org">Python Packaging Index</a>. It has a rather naive
dependency resolution process, but for the most part it works and works well.
It replaced easy_install as the tool to use for installing packages.</p>
<p>While you can use a <code>requirements.txt</code> file with pip to install a "blessed"
list of software there is no good way to "lock" the dependencies of
dependencies without manually adding it to the list of requirements. This ends
up making it very difficult to manage, and it is very difficult to know that
what has been tested is what the user is actually going to get because packages
may be updated at anytime, and re-creating the same exact environment is
difficult and fraught with errors.</p>
<p>This is where <a href="https://github.com/pypa/pipfile">Pipfile</a> is supposed to help. This is a project to add a
new, more descriptive requirements file, as well as allowing for a lockfile
that would lock not just your primary packages you have listed, but also all
dependencies of dependencies all the way down the tree. This helps with
reproducibility and allows for the same installation on two different systems
to have the exact same software/dependencies installed.</p>
<h1>pipenv</h1>
<p>While pip is a great tool, and with the Pipfile changes it would allow for
locking of dependencies, there is one more puzzle piece missing. When
installing packages while you can install them into the global namespace, the
recommended way is to install all packages for a particular tool/project into a
virtual environment.</p>
<p>Normally you'd invoke <a href="https://virtualenv.pypa.io/en/stable/">virtualenv</a>, to create this environment and then
you'd make sure to install all packages within it, thereby isolating it from
the rest of the system.</p>
<p>pipenv automates this for you, as well as using <code>Pipfile</code> it also supports
locking using <code>Pipfile.lock</code> and provides a bunch of tooling around
adding/removing dependencies from a local project.</p>
<p>pipenv allows you to easily create an environment and manage dependencies, but
it makes no effort to solve the problem of distributing and building a package
that may be installed by third parties.</p>
<h1>poetry</h1>
<p>Poetry is a similar project to pipenv, with a major difference being that it
was built to help with distributing/developing applications and building a
distributable package that may then be installed using pip.</p>
<p>Instead of using a <code>Pipfile</code> it uses a recently standardised <code>pyproject.toml</code>
file instead. Like pipenv it also supports locking, and it provides tooling
around adding/removing dependencies as well as managing what versions are
required.</p>
<p>Ultimately those dependencies are going to end up as metadata in a
distributable package.</p>
<p>Poetry makes it easier to manage a software development project, whether that
is for an application using various libraries for internal use, or for
libraries that are going to be distributed to other developers.</p>
<h1>The divide</h1>
<p>This is where the divide really starts, while you can use pipenv with a
standard <a href="https://setuptools.readthedocs.io/en/latest/">setuptools</a> project, any dependencies you add to the Pipfile
using pipenv's tooling will not be listed as a dependencies for your project
when you distribute it, this either means you need to duplicate the list in
both <code>setup.py</code> as well as the <code>Pipfile</code>, or you have to add your current
project as an editable install within your <code>Pipfile</code> which means your <code>Pipfile</code>
is now not as easily distributable.</p>
<p>There are work-arounds that people have used, such as having <code>setup.py</code> read a
<code>requirements.txt</code>, so that you could have all your requirements listed in a
text file, and not in <code>setup.py</code>, but asking to do the same with a <code>Pipfile</code> in
pipenv was met with a <a href="https://github.com/pypa/pipenv/issues/209#issuecomment-332553113">"Do not do this."</a>.</p>
<p>poetry explicitly allows you to add dependencies in one place, and those
dependency listings are then automatically inserted into the package metadata
that is created when you build your distributable package.</p>
<h1>The two use cases</h1>
<p>There are two competing use cases, one is the deployment of software packages
and being able to run them, but not as a developer, the other is a developer of
software packages that needs to define dependencies for the project to run.</p>
<p>pipenv solves the deployment case. If I was a user I could very simply grab a
known good <code>Pipfile.lock</code> and use pipenv to install a known good set of
software, this is great when I am deploying a project. It is the use case that
many in the <a href="https://www.pypa.io/en/latest/">Python Packaging Authority</a> also seem to be optimizing for.</p>
<p>The other use case is for developers that are building new software, either by
using a list of existing packages and deploying privately, or people developing
software for other developers to be published on the Python Packaging Index.</p>
<p>This latter group of people is under represented due to it likely being much
smaller, and existing tools like <code>setuptools</code> and <code>setup.py</code> already providing
a "good enough" experience. Innovation in this area is something that readily
needs to be improved upon to make it easier to create new libraries/packages
that follow best practices. The amount of copy and pastes people have done for
adding a <code>setup.py</code> to their projects or to make something work is long. It's
all a little bit of black magic, and there is a great many things that have
been carried over because of cargo cult programming.</p>
<h1>Explicit mentions by the Python Packaging Authority</h1>
<p>Reading the packaging guide on <a href="https://packaging.python.org/tutorials/managing-dependencies/">managing dependencies</a>, pipenv is the
recommended tool: </p>
<blockquote>
<p>This tutorial walks you through the use of Pipenv to manage dependencies for
an application. It will show you how to install and use the necessary tools
and make strong recommendations on best practices.</p>
</blockquote>
<p>this language, along with what <code>packaging.python.org</code> implies as a URL makes it
difficult as a project maintainer to recommend alternate tools, becuase even if
those tools are superior for the use case we are recommending them for it is
always going to lead to questions from users, such as:</p>
<blockquote>
<p>Why are you not using pipenv, the official tool recommended by Python.org?</p>
</blockquote>
<p>We get similar questions about <code>easy_install</code> vs <code>pip</code> all of the time, as
well as why people should switch, and we can point to various bits of
documentation that explains why pip is a better choice.</p>
<p>If we were to recommend an alternate the appeal to authority that <code>python.org</code>
implies is going to make it much more difficult, and the question will become
"why is the Pylons Project not using recommended tooling?"</p>
<p>poetry is listed as a footnote on that page, alongside <a href="https://github.com/jazzband/pip-tools">pip-tools</a> and
<a href="https://github.com/ofek/hatch">hatch</a>, and is mentioned only for doing library development, with no
mention of other requirements that may make it a much better tool for
developing locally.</p>
<h1>Deployment is not development</h1>
<p>If I am using pipenv with a non-installable project (no setup.py) I end up
having to figure out how to get the code, and the <code>Pipfile</code>/<code>Pipfile.lock</code> to
my environment I am deploying into. pipenv's install provides a way to make
sure to only install if the <code>Pipfile.lock</code> is up to date or otherwise will fail
to continue. If you are using a local project though, and it uses <code>setup.py</code>
the only way that the <code>Pipfile.lock</code> will contain any sub-dependencies of your
<code>setup.py</code> project is if you install it as <code>editable</code>. Otherwise
sub-dependencies are not locked.<sup id="fnref:201805subdepends"><a class="footnote-ref" href="#fn:201805subdepends" rel="footnote">2</a></sup></p>
<p>If I am using poetry I get an pip installable project, but it doesn't contain
any hard pins or lock files. I'd have to distribute <code>pyproject.lock</code> as well as
my wheel. This gets me a little closer, but still no lock file that includes my
newly produced wheel, and has all of its dependencies locked.</p>
<p>The Python Packaging Authority based on Twitter conversations with its members
and the documentation on <code>packaging.python.org</code> suggest using pipenv for
development. pipenv is particular ill-suited for development if the goal is to
create a package to be deployed to production. With two locations to define
dependencies it leaves people scratching their heads as to which is canonical,
and if a dependency is added to <code>Pipfile</code> but not <code>setup.py</code> it may leave a
developer thinking their package is ready for distribution when in reality it
is missing a dependency that is required to run/use said distribution.</p>
<p>At this point using both projects seems like a win-win. Use poetry to
build/develop a package, then use pipenv in the integration phase to create a
<code>Pipfile.lock</code> that is used to deploy in production. This way you get the best
of both worlds. A great tool that can help you register entry points and
another that can help you with deploying a known good set of dependencies.</p>
<p>Interestingly, even the pipenv docs seem to agree that it is a deployment tool:</p>
<blockquote>
<p>Specify your target Python version in your Pipfile’s [requires] section.
Ideally, you should only have one target Python version, as this is a
deployment tool.</p>
<p>-- <a href="https://docs.pipenv.org/basics/#general-recommendations-version-control">Pipenv - General Recommendations &amp; Version Control</a></p>
</blockquote>
<p>Use pipenv if you have a script that requires a couple of dependencies and
doesn't need all of the extra overhead of packaging metadata/packaging. Use
poetry if you want to build a distributable project that can easily be deployed
by others, and use both if you develop a project and need a known good
environment to deploy.</p>
<h1>In summary</h1>
<p>There will likely never be a time that one single tool is considered good
enough, and competition between tools is a way to keep advancing forward.
Packaging in the Python community for a long time has been difficult. Wheels
has made things a little better. pip has made management of installing new
packages easier and improved upon easy_install. Here's to the next evolution.</p>
<hr />
<p>Now, can we talk about standardising on <code>pyproject.toml</code> since that is already
where "project" metadata needs to go, might as well re-use the name instead of
having two different names/files. Oh, and <a href="https://www.python.org/dev/peps/pep-0517/">PEP 517</a> can't come soon enough
so that alternate tools like <a href="https://flit.readthedocs.io/en/latest/">flit</a> can be used instead of
setuptools/<code>setup.py</code>.</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:201805tooldiscussionpyramid">
<p>We created an issue named <a href="https://github.com/Pylons/pyramid/issues/3270">Support poetry,
flit, pipenv, or ...?</a> that attempts to go over the pros and cons of the
various tools and how we currently support our users in our documentation on
building projects using pyramid, including how to create a project that is
distributable. Pyramid heavily uses <a href="http://setuptools.readthedocs.io/en/latest/pkg_resources.html"><code>pkg_resources</code></a> and entry points.
The way to register the entry points is to have an installable package.</p>
<p>The framework is flexible enough that there is no requirement for entry
points, but at that point you are in territory where the default tooling
provided by the project will not work, and some of the convenience
tools/functionality that Pyramid provides it's users/developers is not
available.&#160;<a class="footnote-backref" href="#fnref:201805tooldiscussionpyramid" rev="footnote" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
<li id="fn:201805subdepends">
<p>See documentation for <a href="https://docs.pipenv.org/basics/#editable-dependencies-e-g-e">Editable Dependencies (e.g.  <code>-e
  .</code>)</a> which as of this writing states: </p>
<blockquote>
<p>Sub-dependencies are <strong>not</strong> added to the Pipfile.lock if you leave the
-e option out.</p>
</blockquote>
<p><a class="footnote-backref" href="#fnref:201805subdepends" rev="footnote" title="Jump back to footnote 2 in the text">&#8617;</a></p>
</li>
</ol>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Mac OS X El Capitan Installer Removes Custom Group ID and Membership</title>
      <link>http://funcptr.net/2015/10/02/mac-os-x-el-capitan-installer-removes-custom-group-id-and-membership</link>
      <pubDate>Fri, 02 Oct 2015 01:23:45 MDT</pubDate>
      <category><![CDATA[Mac OS X]]></category>
      <category><![CDATA[dscl]]></category>
      <category><![CDATA[El Capitan]]></category>
      <guid isPermaLink="true">http://funcptr.net/2015/10/02/mac-os-x-el-capitan-installer-removes-custom-group-id-and-membership</guid>
      <description>Mac OS X El Capitan Installer Removes Custom Group ID and Membership</description>
      <content:encoded><![CDATA[<p>As always, after Apple releases their new operating system, my systems are
upgraded. This time the upgrade was less of a surprise in terms of what it
brings because I'd been beta testing the new release for the past couple of
weeks, however I was still caught off guard.</p>
<p>On OS X, by default all user accounts start at ID 501 and count up, so if you
have two accounts, you will have user ID 501 and 502 in use. For most people
they will most likely never change this, and all is well. The default group ID
for all new user accounts is <code>staff</code> which has a group ID of 20. So if you have
a single account named for example <code>janedoe</code> her user ID would be 501 and her
group ID would be 20 (<code>staff</code>).</p>
<p>Coming from a FreeBSD world and running a lot of FreeBSD systems, user accounts
start at 1001, and count up. When you create a new user account on FreeBSD, by
default that user is also added to a group with the same name as the username,
with the same ID. So you end up with an account with ID 1001 and default group
ID 1001. Using the same example, a user named <code>janedoe</code> would have a user ID of
1001, and a group ID of 1001 (<code>janedoe</code>).</p>
<p>When I first installed OS X, and almost every single new installation since, I
have followed these steps to change my user ID and group ID to match those on
my FreeBSD systems:</p>
<ol>
<li>Assumption is that you have a separate user account other than the one you
    are about to modify that you can temporarily use that has administrator
    privileges on the local Mac; I create an "Administrator" account for that
    reason.</li>
<li>System Preferences</li>
<li>Users and Groups</li>
<li>Click the <code>+</code> (You may need to click the lock in the bottom left first)</li>
<li>Change the dropdown to <code>group</code></li>
<li>Enter Full Name: <code>janedoe</code></li>
<li>Create group</li>
<li>Right click on group (<code>janedoe</code>)</li>
<li>Advanced Options...</li>
<li>Change the Group ID to 1001</li>
<li>Okay</li>
<li>Right click on user (<code>janedoe</code>)</li>
<li>Advanced Options...</li>
<li>Change User ID from 501 to 1001</li>
<li>Change Group from <code>staff</code> to <code>janedoe</code></li>
<li>Okay</li>
<li>Close System Preferences</li>
<li>Open Terminal, become root user (sudo su)</li>
<li><code>cd /Users/janedoe</code></li>
<li>find . -uid 501 -print0 | xargs -0 chown 1001:1001</li>
</ol>
<p>This allows me to have the same user ID and group ID on both my Mac OS X and on
FreeBSD, thereby making it easier to use tools like rsync that keeps ownership
and permissions, as well as using NFS. Other ways to do something similar is
using LDAP/Kerberos with shared directory service, but that is a little heavy
handed for a home network.</p>
<p>This has worked for me without issues since OS X 10.8, even upgrading from 10.8 to
10.9 and then 10.10 did not change anything. However as soon as I did the
upgrade to El Capitan (10.11) I noticed that all of my <code>ls -lah</code> output looked
like this:</p>
<div class="codehilite"><pre><span></span>drwxr-xr-x+  13 xistence  1001   442B Oct  1 16:58 Desktop
drwx------+  28 xistence  1001   952B Aug 31 12:17 Documents
drwx------+  89 xistence  1001   3.0K Oct  1 15:56 Downloads
drwx------@  72 xistence  1001   2.4K Oct  2 00:16 Library
</pre></div>


<p>and <code>id</code> provided this valuable output:</p>
<div class="codehilite"><pre><span></span>uid=1001(xistence) gid=20(xistence) groups=20(xistence),12(everyone),61(localaccounts),399(com.apple.access_ssh),402(com.apple.sharepoint.group.2),401(com.apple.sharepoint.group.1),100(_lpoperator)
</pre></div>


<p>Wait, what happened to the <code>staff</code> group that I am supposed to be a member of,
and why is my <code>xistence</code> group ID now stating it is 20 and not 1001 as I was
expecting.</p>
<p>I wondered if the upgrade had messed up my group somehow, and it was
confirmed when I checked with <code>dscl</code>.</p>
<div class="codehilite"><pre><span></span>$ dscl . -read /Groups/xistence
<span class="o">[</span>...<span class="o">]</span>
Password: *
PrimaryGroupID: <span class="m">20</span>
RealName: xistence
RecordName: xistence
RecordType: dsRecTypeStandard:Groups
</pre></div>


<p>Do note that the group <code>xistence</code> does not show up in <em>System Preferences</em> -&gt;
<em>Users and Groups</em>, so we'll have to do some command line magic.</p>
<p>Well, that's worrisome, why is this matching a built-in group's ID? Specifically
let's check the <code>staff</code> group and make sure it still has the appropriate group
ID.</p>
<div class="codehilite"><pre><span></span>$ dscl . -read /Groups/staff
<span class="o">[</span>...<span class="o">]</span>
GroupMembership: root
Password: *
PrimaryGroupID: <span class="m">20</span>
RealName: Staff
RecordName: staff BUILTIN<span class="se">\U</span>sers
RecordType: dsRecTypeStandard:Groups
</pre></div>


<p>Next I had to check to see what my user account was set to as the default group
ID:</p>
<div class="codehilite"><pre><span></span>$ dscl . -read /Users/xistence
<span class="o">[</span>...<span class="o">]</span>
NFSHomeDirectory: /Users/xistence
Password: ********
PrimaryGroupID: <span class="m">20</span>
RealName:
 Bert JW Regeer
RecordName: xistence bertjw@regeer.org com.apple.idms.appleid.prd.53696d524c62372b48344a53755864634e4f374b32513d3d
RecordType: dsRecTypeStandard:Users
UniqueID: <span class="m">1001</span>
UserShell: /bin/bash
</pre></div>


<p>Well, that is not entirely what I was expecting either, at last it didn't touch
my user ID. Time to fix things.</p>
<p>First let's change the <code>xistence</code> group's group ID to 1001, and then change the
Primary Group ID for the user <code>xistence</code> to group ID 1001.</p>
<div class="codehilite"><pre><span></span># dscl . -change /Groups/xistence PrimaryGroupID 20 1001
# dscl . -change /Users/xistence PrimaryGroupID 20 1001
</pre></div>


<p>After that <code>id</code> looked a little bit more sane:</p>
<div class="codehilite"><pre><span></span>uid=1001(xistence) gid=1001(xistence) groups=1001(xistence),12(everyone),61(localaccounts),399(com.apple.access_ssh),402(com.apple.sharepoint.group.2),401(com.apple.sharepoint.group.1),100(_lpoperator)
</pre></div>


<p>However now the group <code>staff</code> is missing from the list of groups that the user
<code>xistence</code> is a member of, which I don't think will hurt anything, but we still
want to be able to read/write any folders that are designated as <code>staff</code>
elsewhere in the OS, and any other privileges that entails. So let's add the
user <code>xistence</code> to the <code>staff</code> group:</p>
<div class="codehilite"><pre><span></span># dscl . -append /Groups/staff GroupMembership xistence
</pre></div>


<p>Let's verify, and check <code>id</code> again:</p>
<div class="codehilite"><pre><span></span>uid=1001(xistence) gid=1001(xistence) groups=1001(xistence),12(everyone),20(staff),61(localaccounts),399(com.apple.access_ssh),402(com.apple.sharepoint.group.2),401(com.apple.sharepoint.group.1),100(_lpoperator)
</pre></div>


<p>For this to fully take effect, log out and log back in. This will make sure
that all new files have the correct user ID/group ID set.</p>
<p>After the change to the Group ID, the group still doesn't show up in <em>System
Preferences</em> -&gt; <em>Users and Groups</em>, which I find weird since it is not a
built-in group.</p>
<p>Luckily everything is back to the way it was before the upgrade, and my backup
scripts and NFS shares work again without issues.</p>]]></content:encoded>
    </item>
    <item>
      <title>Cobbler with CentOS 7 failure to boot/kickstart</title>
      <link>http://funcptr.net/2015/07/08/cobbler-with-centos-7-failure-to-boot/kickstart</link>
      <pubDate>Wed, 08 Jul 2015 19:54:43 MDT</pubDate>
      <category><![CDATA[cobbler]]></category>
      <category><![CDATA[rhel]]></category>
      <category><![CDATA[centos]]></category>
      <guid isPermaLink="true">http://funcptr.net/2015/07/08/cobbler-with-centos-7-failure-to-boot/kickstart</guid>
      <description>Cobbler with CentOS 7 failure to boot/kickstart</description>
      <content:encoded><![CDATA[<p>Over the past week I've been working on building out an instance of Cobbler and
testing some of the provisioning that it is able to do. One of the operating
systems that I wanted to deploy is CentOS 7.</p>
<p>After I imported the system into cobbler, it correctly showed up in the
pxelinux boot menu and it would happily load the kernel and the initrd, however
after initial bootup it would throw the following error message:</p>
<div class="codehilite"><pre><span></span>dracut-initqueue[867]: Warning: Could not boot.
dracut-initqueue[867]: Warning: /dev/root does not exist

         Starting Dracut Emergency Shell...
Warning: /dev/root does not exist

Generating &quot;/run/initramfs/rdsosreport.txt&quot;


Entering emergency mode. Exit the shell to continue.
Type &quot;journalctl&quot; to view the system logs.
You might want to save &quot;/run/initramfs/rdsosreport.txt&quot; to a USB stick or /boot
after mounting them and attach it to a bug report
</pre></div>


<p>After that it gives you a root shell.</p>
<p>Some Google searching led me to an <a href="https://lists.fedorahosted.org/pipermail/cobbler/2014-August/009647.html">mailing list post for Cobbler</a> where
someone mentioned that adding <code>ksdevice=link</code> to the Cobbler profile allowed
the system to boot without issues.</p>
<p>However before I just implement a change I want to know why that fixes the
issue, so I searched Google for "kickstart ksdevice" and found <a href="https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6A/html/Installation_Guide/s1-kickstart2-startinginstall.html">Red Hat's
documentation on starting a kickstart</a>. Searching that page for "ksdevice"
led me to this section:</p>
<blockquote>
<p><code>ksdevice=&lt;device&gt;</code></p>
<p>The installation program uses this network device to connect to the network. You can specify the device in one of five ways:</p>
<ul>
<li>the device name of the interface, for example, <code>eth0</code></li>
<li>the MAC address of the interface, for example, <code>00:12:34:56:78:9a</code></li>
<li>the keyword <code>link</code>, which specifies the first interface with its link in the up state</li>
<li>the keyword <code>bootif</code>, which uses the MAC address that pxelinux set in the BOOTIF variable. Set IPAPPEND 2 in your pxelinux.cfg file to have pxelinux set the BOOTIF variable.</li>
<li>the keyword <code>ibft</code>, which uses the MAC address of the interface specified by iBFT</li>
</ul>
<p>For example, consider a system connected to an NFS server through the eth1 device. To perform a kickstart installation on this system using a kickstart file from the NFS server, you would use the command <code>ks=nfs:&lt;server&gt;:/&lt;path&gt; ksdevice=eth1</code> at the boot: prompt.</p>
</blockquote>
<p>While <code>ksdevice=link</code> would work for some of the machines I am deploying, it
wouldn't work for most since they have multiple interfaces and each one of
those interfaces would have link, what I really wanted was <code>ksdevice=bootif</code>,
which is the most sensible default.</p>
<p>So I modified the profile with <code>ksdevice=link</code> just to test, and that worked
without issues, so then I modified the profile and added <code>ksdevice=link</code>, and
this failed.</p>
<p>I figured I should check the <code>pxelinux.cfg/default</code> file that Cobbler generates
upon issuing a <code>cobbler sync</code> and verify that <code>ksdevice=bootif</code> is actually
listed correctly.</p>
<p>What I found was this:</p>
<div class="codehilite"><pre><span></span>LABEL CentOS-7.1-x86_64
        kernel /images/CentOS-7.1-x86_64/vmlinuz
        MENU LABEL CentOS-7.1-x86_64
        append initrd=/images/CentOS-7.1-x86_64/initrd.img ksdevice=<span class="cp">${</span><span class="n">net0</span><span class="o">/</span><span class="n">mac</span><span class="cp">}</span> lang=  kssendmac text  ks=http://10.10.10.1/cblr/svc/op/ks/profile/CentOS-7.1-x86_64
        ipappend 2
</pre></div>


<p>This has a <code>ksdevice=${net0/mac}</code> which is not what I had put in the profile,
overwriting <code>ksdevice</code> in the profile with <code>ksdevice=link</code> did correctly put
that into the <code>pxelinux.cfg/default</code> file, so Cobbler was overwriting my change
somehow.</p>
<p>A quick search of <code>${net0/mac}</code> led me to a page about <a href="http://etherboot.org/wiki/commandline">gPXE commandline
items</a> that contained the same variable. At which point I remembered that in
Cobbler you set up your profile to be gPXE enabled or not. The default when you
import an image is to enable gPXE support.</p>
<div class="codehilite"><pre><span></span>cobbler profile report  --name=CentOS-7.1-x86_64

Name                           : CentOS-7.1-x86_64
TFTP Boot Files                : {}
Comment                        : 
DHCP Tag                       : default
Distribution                   : CentOS-7.1-x86_64
Enable gPXE?                   : True
Enable PXE Menu?               : 1
[...]
</pre></div>


<p>So let's modify the profile to disable gPXE support:</p>
<div class="codehilite"><pre><span></span>cobbler profile edit --name=CentOS-7.1-x86_64 --enable-gpxe=False
cobbler sync
</pre></div>


<p>Verify that the change was made:</p>
<div class="codehilite"><pre><span></span>cobbler profile report  --name=CentOS-7.1-x86_64

[...]
Enable gPXE?                   : False
[...]
</pre></div>


<p>Then let's take a look at our <code>pxelinux.cfg/default</code> file and make sure that it
looks correct:</p>
<div class="codehilite"><pre><span></span>LABEL CentOS-7.1-x86_64
        kernel /images/CentOS-7.1-x86_64/vmlinuz
        MENU LABEL CentOS-7.1-x86_64
        append initrd=/images/CentOS-7.1-x86_64/initrd.img ksdevice=bootif lang=  kssendmac text  ks=http://10.10.10.1/cblr/svc/op/ks/profile/CentOS-7.1-x86_64
        ipappend 2
</pre></div>


<p>This time our ksdevice is correctly set. Upon rebooting my PXE booted server it
picked up the correct interface, made a DHCP request and kickstarted the server
using the provided kickstart file, and installation completed successfully.</p>
<p>So unless you chain-boot gPXE from pxelinux by default, make sure that your
profiles are not set to be gPXE enabled if you want to use them directly from
the pxelinux menu.</p>
<p>While researching more about this article, I found a blog post by <a href="https://blog.vladionescu.com/pxe-installing-rhel-7-from-cobbler-2-6/">Vlad Ionescu
about PXE installing RHEL 7 from Cobbler</a> where he suggests disabling
<code>ksdevice</code> entirely and adding an extra <code>inst.repo</code> variable to the kernel
command line, however on older versions of CentOS 7 and Red Hat Enterprise
Linux 7 there is a <a href="https://bugzilla.redhat.com/show_bug.cgi?id=1096846">bug report</a> that shows that an empty <code>ksdevice</code> could
cause anaconda to crash, and setting a manual <code>inst.repo</code> for every profile
seems like overkill when just disabling gPXE for the profile also solves the
problem.</p>]]></content:encoded>
    </item>
    <item>
      <title>Neutron L3 agent with multiple provider networks</title>
      <link>http://funcptr.net/2014/09/29/neutron-l3-agent-with-multiple-provider-networks</link>
      <pubDate>Mon, 29 Sep 2014 18:11:00 MDT</pubDate>
      <category><![CDATA[openstack]]></category>
      <category><![CDATA[openvswitch]]></category>
      <category><![CDATA[neutron]]></category>
      <category><![CDATA[linux]]></category>
      <guid isPermaLink="true">http://funcptr.net/2014/09/29/neutron-l3-agent-with-multiple-provider-networks</guid>
      <description>Neutron L3 agent with multiple provider networks</description>
      <content:encoded><![CDATA[<p>Due to requirements outside of my control, there was a requirement to run
multiple "provider" networks each with each providing their own floating
address pool from a single network node, I wanted to do this as simply as
possible using a single l3 agent rather than having to figure out how to get
systemd to start multiple with different configuration files.</p>
<p>Currently I've installed and configured an OpenStack instance that looks like
this:</p>
<div class="codehilite"><pre><span></span>+---------------------+
|                     |
|                  +--+----+
|                  |       |
|      +-----------+-+  +--+----------+
|      | Compute     |  | Compute     |
|      |     01      |  |     02      |
|      +------+------+  +-----+-------+
|             |               |
|             |               +----------+
|             +------------+--+          |
|                          |             |
| +-------------+    +-----+-------+     |
| | Controller  |    |   Network   |     |
| |             |    |             |     +---+  Tenant Networks (vlan tagged) (vlan ID&#39;s 350 - 400)
| +-----+----+--+    +------+----+-+
|       |    |              |    |
|       |    |              |    +-----------+  Floating Networks (vlan tagged) (vlan ID&#39;s 340 - 349)
|       |    |              |
|       |    |              |
+------------+--------------+----------------+  Management Network (10.5.2.0/25)
        |
        |
        +------------------------------------+  External API Network (10.5.2.128/25)
</pre></div>


<p>There are two compute nodes, a controller node that runs all of the API
services, and a network node that is strictly used for providing network
functions (routers, load balancers, firewalls, all that fun stuff!).</p>
<p>There are two flat networks that provide the following:</p>
<ol>
<li>External API access</li>
<li>A management network that OpenStack uses internally to communicate between
    instances and to manage it, which is not accessible from the other three
    networks.</li>
</ol>
<p>The other two networks are both vlan tagged:</p>
<ol>
<li>Tenant networks, with the possibility of 50 vlan ID's</li>
<li>Floating networks, with existing vlan ID's for existing networks</li>
</ol>
<p>Since the OpenStack Icehouse release, the l3 agent has supported the ability to
use the Open vSwitch configuration to specify how traffic should be routed
rather than statically defining that a single l3 agent routes certain traffic
to a single Linux bridge. Setting this up is fairly simple if you follow the
documentation, with one caveat, variables you think would be defined to no
value, actually have a value and thus need to be explicitly zeroed out.</p>
<h1>On the network node</h1>
<p>First, we need to configure the l3 agent, so let's set some extra variables in
<code>/etc/neutron/l3-agent.ini</code>:</p>
<div class="codehilite"><pre><span></span>gateway_external_network_id =
external_network_bridge =
</pre></div>


<p>It is important that these two are set, not left commented out, unfortunately
when commented out they have some defaults set and it will fail to work, so
explicitly setting them to blank will fix that issue.</p>
<p>Next, we need to set up our Open vSwitch configuration. In
<code>/etc/neutron/plugin.ini</code> the following needs to be configured:</p>
<ul>
<li><code>bridge_mappings</code></li>
<li><code>network_vlan_ranges</code></li>
</ul>
<p>Note, that these may already be configured, in which case there is nothing left
to do. Mine currently looks like this:</p>
<div class="codehilite"><pre><span></span>bridge_mappings = tenant1:br-tnt,provider1:br-ex
</pre></div>


<p>This basically specifies that any networks created under "provider name"
<code>tenant1</code> are going to be mapped to the Open vSwitch bridge <code>br-tnt</code> and any
networks with "provider name" <code>provider1</code> will be mapped to <code>br-ex</code>.</p>
<p><code>br-tnt</code> is mapped to my tenant network and on the switch has vlan ID's 350 -
400 assigned, and <code>br-ex</code> has vlan ID's 340 - 349 assigned.</p>
<p>Following the above knowledge, my <code>network_vlan_ranges</code> is configured as such:</p>
<div class="codehilite"><pre><span></span>network_vlan_ranges = tenant1:350:400,provider1:340:349
</pre></div>


<p>Make sure to restart all neutron services:</p>
<div class="codehilite"><pre><span></span>openstack-service restart neutron
</pre></div>


<h1>On the controller (where <code>neutron-server</code> lives)</h1>
<p>On the controller we just need to make sure that our <code>network_vlan_ranges</code>
matches what is on the network node, with one exception, we do not list our
<code>provider1</code> vlan ranges since we don't want to make those available to
accidentally be assigned when a regular tenant creates a new network.</p>
<p>So our configuration should list:</p>
<div class="codehilite"><pre><span></span>network_vlan_ranges = tenant1:350:400
</pre></div>


<p>Make sure that all neutron services are restarted:</p>
<div class="codehilite"><pre><span></span>openstack-service restart neutron
</pre></div>


<h1>Create the Neutron networks</h1>
<p>Now, as an administrative user we need to create the provider networks.</p>
<div class="codehilite"><pre><span></span>source ~/keystonerc_admin

neutron net-create &quot;192.168.1.0/24-floating&quot; \
--router:external True \
--provider:network_type vlan \
--provider:physical_network provider1 \
--provider:segmentation_id 340

neutron net-create &quot;192.168.2.0/24-floating&quot; \
--router:external True \
--provider:network_type vlan \
--provider:physical_network provider1 \
--provider:segmentation_id 341
</pre></div>


<p>Notice how we've created two networks, given them each individual names (I like
to use the name of the network they are going to be used for) and have been
attached to the <code>provider1</code>. Note that <code>provider1</code> is completely
administratively defined, and could just as well have been <code>physnet1</code>, so long
as it is consistent across all of the configuration files.</p>
<p>Now let's create subnets on this network:</p>
<div class="codehilite"><pre><span></span>neutron subnet-create &quot;192.168.1.0/24-floating&quot; 192.168.1.0/24 \
--allocation-pool start=192.168.1.4,end=192.168.1.254 \
--disable-dhcp --gateway 192.168.1.1

neutron subnet-create &quot;192.168.2.0/24-floating&quot; 192.168.2.0/24 \
--allocation-pool start=192.168.2.4,end=192.168.2.254 \
--disable-dhcp --gateway 192.168.2.1
</pre></div>


<p>Now that these networks are defined, you should be able to have tenants create
routers and set their gateways to either of these new networks by selecting
from the drop-down in Horizon or by calling <code>neutron router-gateway-set &lt;router
id&gt; &lt;network id&gt;</code> on the command line.</p>
<p>The l3 agent will automatically configure and set up the router as required on
the network node, and traffic will flow to either vlan 340 or vlan 341 as
defined above depending on what floating network the user uses as a gateway.</p>
<p>This drastically simplifies the configuration of multiple floating IP networks
since no longer is there a requirement to start up and configure multiple l3
agents each with their own network ID configured. This makes configuration less
brittle and easier to maintain over time.</p>]]></content:encoded>
    </item>
    <item>
      <title>OpenStack resizing of instances</title>
      <link>http://funcptr.net/2014/09/29/openstack-resizing-of-instances</link>
      <pubDate>Mon, 29 Sep 2014 17:10:00 MDT</pubDate>
      <category><![CDATA[openstack]]></category>
      <category><![CDATA[ssh]]></category>
      <category><![CDATA[linux]]></category>
      <guid isPermaLink="true">http://funcptr.net/2014/09/29/openstack-resizing-of-instances</guid>
      <description>OpenStack resizing of instances</description>
      <content:encoded><![CDATA[<p>One thing that is not always adequately explained in the OpenStack
documentation is how exactly instance resizing works, and what is required,
especially while using KVM as the virtualisation provider, with multiple
compute nodes.</p>
<p>You might find something similiar to the following in your logs, and no good
documentation on how to fix it.</p>
<div class="codehilite"><pre><span></span>ERROR nova.compute.manager [req-7cb1c029-beb4-4905-a9d9-62d488540eda f542d1b5afeb4908b8b132c4486f9fa8 c2bfab5ad24642359f43cdff9bb00047] [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b] Setting instance vm_state to ERROR
TRACE nova.compute.manager [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b] Traceback (most recent call last):
TRACE nova.compute.manager [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b]   File &quot;/usr/lib/python2.7/site-packages/nova/compute/manager.py&quot;, line 5596, in _error_out_instance_on_exception
TRACE nova.compute.manager [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b]     yield
TRACE nova.compute.manager [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b]   File &quot;/usr/lib/python2.7/site-packages/nova/compute/manager.py&quot;, line 3459, in resize_instance
TRACE nova.compute.manager [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b]     block_device_info)
TRACE nova.compute.manager [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b]   File &quot;/usr/lib/python2.7/site-packages/nova/virt/libvirt/driver.py&quot;, line 4980, in migrate_disk_and_power_off
TRACE nova.compute.manager [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b]     utils.execute(&#39;ssh&#39;, dest, &#39;mkdir&#39;, &#39;-p&#39;, inst_base)
TRACE nova.compute.manager [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b]   File &quot;/usr/lib/python2.7/site-packages/nova/utils.py&quot;, line 165, in execute
TRACE nova.compute.manager [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b]     return processutils.execute(*cmd, **kwargs)
TRACE nova.compute.manager [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b]   File &quot;/usr/lib/python2.7/site-packages/nova/openstack/common/processutils.py&quot;, line 193, in execute
TRACE nova.compute.manager [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b]     cmd=&#39; &#39;.join(cmd))
TRACE nova.compute.manager [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b] ProcessExecutionError: Unexpected error while running command.
TRACE nova.compute.manager [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b] Command: ssh 10.5.2.20 mkdir -p /var/lib/nova/instances/99736f90-db0f-4cba-8f44-a73a603eee0b
TRACE nova.compute.manager [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b] Exit code: 255
TRACE nova.compute.manager [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b] Stdout: &#39;&#39;
TRACE nova.compute.manager [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b] Stderr: &#39;Host key verification failed.\r\n&#39;
TRACE nova.compute.manager [instance: 99736f90-db0f-4cba-8f44-a73a603eee0b] 
ERROR oslo.messaging.rpc.dispatcher [-] Exception during message handling: Unexpected error while running command.
Command: ssh 10.5.2.20 mkdir -p /var/lib/nova/instances/99736f90-db0f-4cba-8f44-a73a603eee0b
Exit code: 255
Stdout: &#39;&#39;
Stderr: &#39;Host key verification failed.\r\n&#39;
</pre></div>


<p>When OpenStack's nova is instructed to resize an instance it will also change
the host it is running on, almost never will it schedule the instance on the
same host and do the resize on the same host it already exists. There is a
configuration flag to change this, however in my case I would rather the
scheduler be run again, especially if the instance size is changing
drastically. During the resize process, the node where the instance is
currently running will use SSH to connect to the instance where the resized
instance will live, and copy over the instance and associated files.</p>
<p>There are a couple of assumptions I will be making:</p>
<ol>
<li>Your <code>nova</code>, and <code>qemu</code> user both have the same UID on all compute nodes</li>
<li>The path for your instances is the same on all of your compute nodes</li>
</ol>
<h1>Configure the nova user</h1>
<p>First things first, let's make sure our <code>nova</code> user has an appropriate shell set:</p>
<div class="codehilite"><pre><span></span>cat /etc/passwd | grep nova
</pre></div>


<p>Verify that the last entry is <code>/bin/bash</code>.</p>
<p>If not, let's modify the user and make it so:</p>
<div class="codehilite"><pre><span></span>usermod -s /bin/bash nova
</pre></div>


<h1>Generate SSH key and configuration</h1>
<p>After doing this the next steps are all run as the <code>nova</code> user.</p>
<div class="codehilite"><pre><span></span>su - nova
</pre></div>


<p>We need to generate an SSH key:</p>
<div class="codehilite"><pre><span></span>ssh-keygen -t rsa
</pre></div>


<p>Follow the directions, and save the key WITHOUT a passphrase.</p>
<p>Next up we need to configure SSH to not do host key verification, unless you
want to manually SSH to all compute nodes that exist and accept the key (and
continue to do so for each new compute node you add).</p>
<div class="codehilite"><pre><span></span>cat &lt;&lt; EOF &gt; ~/.ssh/config
Host *
    StrictHostKeyChecking no
    UserKnownHostsFile=/dev/null
EOF
</pre></div>


<p>Next we need to make sure we copy the the contents of <code>id_rsa.pub</code> to
<code>authorized_keys</code> and set the mode on it correctly.</p>
<div class="codehilite"><pre><span></span>cat ~/.ssh/id_rsa.pub &gt; .ssh/authorized_keys
chmod 600 .ssh/authorized_keys
</pre></div>


<p>This should be all the configuration for SSH you need to do. Now comes the
import part, you will need to tar up and copy the <code>~nova/.ssh</code> directory to
every single compute node you have provisioned. This way all compute nodes will
be able to SSH to the remote host to run the commands required to copy an
instance over, and resize it.</p>
<h1>Reset state on existing ERROR'ed instances</h1>
<p>If you have any instances that are currently in the <code>ERROR</code> state due to a
failed resize, you will be able to issue the following command to reset the
state back to running and try again:</p>
<div class="codehilite"><pre><span></span>nova reset-state --active &lt;ID of instance&gt;
</pre></div>


<p>This will start the instance, and you will be able to once again issue the
resize command to resize the instance.</p>]]></content:encoded>
    </item>
    <item>
      <title>Build numbers in binaries using waf</title>
      <link>http://funcptr.net/2014/06/10/build-numbers-in-binaries-using-waf</link>
      <pubDate>Tue, 10 Jun 2014 17:32:03 MDT</pubDate>
      <category><![CDATA[waf]]></category>
      <category><![CDATA[git]]></category>
      <category><![CDATA[C++]]></category>
      <guid isPermaLink="true">http://funcptr.net/2014/06/10/build-numbers-in-binaries-using-waf</guid>
      <description>Build numbers in binaries using waf</description>
      <content:encoded><![CDATA[<p>My build system of choice these days for any C++ project is <a href="https://code.google.com/p/waf/">waf</a>. One of
the things I always like havig is the build number included in the final
binary, so that with a simple <code>./binary --version</code> or even <code>./binary</code> the
version is printed that it was built from. This can make it much simpler to
debug any potential issues, especially if fixes may have already been made but
a bad binary was deployed.</p>
<h1>Setup the wscript</h1>
<p>Make sure that your wscript somewhere near the top contains the following:</p>
<div class="codehilite"><pre><span></span><span class="n">APPNAME</span> <span class="o">=</span> <span class="s1">&#39;myapp&#39;</span>
<span class="n">VERSION</span> <span class="o">=</span> <span class="s1">&#39;0.0.0&#39;</span>
</pre></div>


<p>Then in your <code>configure(cfg)</code> add the following:</p>
<div class="codehilite"><pre><span></span><span class="n">cfg</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">VERSION</span> <span class="o">=</span> <span class="n">VERSION</span>
<span class="n">cfg</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">APPNAME</span> <span class="o">=</span> <span class="n">APPNAME</span>

<span class="n">git_version</span> <span class="o">=</span> <span class="n">try_git_version</span><span class="p">()</span>

<span class="k">if</span> <span class="n">git_version</span><span class="p">:</span>
    <span class="n">cfg</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">VERSION</span> <span class="o">+=</span> <span class="s1">&#39;-&#39;</span> <span class="o">+</span> <span class="n">git_version</span>
</pre></div>


<p>The <code>try_git_version()</code> function is fairly simple and looks like this:</p>
<div class="codehilite"><pre><span></span><span class="k">def</span> <span class="nf">try_git_version</span><span class="p">():</span>
    <span class="kn">import</span> <span class="nn">os</span>
    <span class="kn">import</span> <span class="nn">sys</span>

    <span class="n">version</span> <span class="o">=</span> <span class="bp">None</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">version</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">popen</span><span class="p">(</span><span class="s1">&#39;git describe --always --dirty --long&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="k">print</span> <span class="n">e</span>
    <span class="k">return</span> <span class="n">version</span>
</pre></div>


<p>It runs <code>git describe --always --dirty --long</code> which will return something
along these lines: <code>401b85f-dirty</code>. If you have any annoted tags, it will
return the tag name as well.</p>
<p>If <code>git</code> is not installed, or it is not a valid <code>git</code> directory, then it will
simply return <code>None</code>. At that point all we have to go on is the <code>VERSION</code>
variable set at the top of the wscript.</p>
<p>Now that we have our configuration environment set up with the <code>VERSION</code> we
want to get that into a file that we can then include in our C++ source code.</p>
<h1>Create a <code>build_version.h.in</code> file</h1>
<div class="codehilite"><pre><span></span><span class="cp">#ifndef BUILD_VERSION_H_IN_941AD1F24D0A9D</span>
<span class="cp">#define BUILD_VERSION_H_IN_941AD1F24D0A9D</span>

<span class="kt">char</span> <span class="n">VERSION</span><span class="p">[]</span> <span class="o">=</span> <span class="s">&quot;@VERSION@&quot;</span><span class="p">;</span>

<span class="cp">#endif </span><span class="cm">/* BUILD_VERSION_H_IN_941AD1F24D0A9D */</span><span class="cp"></span>
</pre></div>


<h1>Add the following to <code>build(ctx)</code></h1>
<div class="codehilite"><pre><span></span>ctx(features=&#39;subst&#39;,
        source=&#39;build_version.h.in&#39;,
        target=&#39;build_version.h&#39;,
        VERSION = ctx.env[&#39;VERSION&#39;],
        )
</pre></div>


<p>This uses the substitution feature to transform <code>build_version.h.in</code> into
<code>build_version.h</code>, while inserting the version into the file.</p>
<h1>Include <code>build_version.h</code> in your source code</h1>
<div class="codehilite"><pre><span></span><span class="cp">#include</span> <span class="cpf">&quot;build_version.h&quot;</span><span class="cp"></span>
</pre></div>


<p>And add something along these lines to your <code>main()</code>:</p>
<div class="codehilite"><pre><span></span><span class="n">std</span><span class="o">::</span><span class="n">cerr</span> <span class="o">&lt;&lt;</span> <span class="s">&quot;Version: &quot;</span> <span class="o">&lt;&lt;</span> <span class="n">VERSION</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
</pre></div>


<p>This will print out the <code>VERSION</code> that has been stored in <code>build_version.h</code>.</p>
<h1>Full example</h1>
<p>Check out my <a href="https://github.com/bertjwregeer/mdns-announce">mdns-announce</a> project on Github for an example of how this is
implemented.</p>]]></content:encoded>
    </item>
    <item>
      <title>Tamper proof session cookies and session storage</title>
      <link>http://funcptr.net/2014/06/10/tamper-proof-session-cookies-and-session-storage</link>
      <pubDate>Tue, 10 Jun 2014 17:09:45 MDT</pubDate>
      <category><![CDATA[secure programming]]></category>
      <category><![CDATA[good practices]]></category>
      <category><![CDATA[sessions]]></category>
      <guid isPermaLink="true">http://funcptr.net/2014/06/10/tamper-proof-session-cookies-and-session-storage</guid>
      <description>Tamper proof session cookies and session storage</description>
      <content:encoded><![CDATA[<p>As a follow-up to my previous article regarding <a href="http://funcptr.net/2013/08/25/user-sessions,-what-data-should-be-stored-where-/">User sessions, what data
should be stored where?</a>, I wanted to discuss how to store the session, and
how to generate cookies that are tamper proof.</p>
<h1>What are we trying to accomplish?</h1>
<p>Ultimately we want to be able to have X amount pieces of data that are tied to
a particular user. Unfortunately due the fact that HTTP is a stateless protocol
we have to use cookies. Cookies are small little pieces of data that are
transmitted from the server to the client (generally done once), and then upon
the user coming back to the website they are transmitted from the client to the
server. This allows us to uniquely track a single user across connections to
our website.</p>
<p>If the website allows a user to authenticate and the fact that they are
authenticated is stored in the session, we also want to make sure that we can
aggressively expire a session, if this is possible depends on our session
storage.</p>
<h1>Session storage</h1>
<p>There are a multitude of ways to store the session data, but it ultimately
boils down to server-side or client-side. Server-side can be done in Cassandra,
Memcache, Redis or even in a SQL database.</p>
<h2>Server-side storage</h2>
<p>The main one that has
been used for years is to use server side storage. Storing a small file on the
servers hard drive that contains the data, and the client is sent a cookie that
contains a unique identifier that is linked to the on-disk storage.</p>
<p>For example:</p>
<div class="codehilite"><pre><span></span>1 =&gt; /tmp/session_1
2 =&gt; /tmp/session_2
...
N =&gt; /tmp/session_N
</pre></div>


<h3>Easily expire sessions</h3>
<p>The upside to server-side storage is that it is possible for us to very easily
expire a session, simply remove the associated file/data that is stored and the
users session has now become invalid.</p>
<h2>Client-side storage</h2>
<p>The other method that has recently started being used more to make it easier to
scale the server side is to store session data encoded in base64 in the cookie
itself. In this case there is no unique session ID, and no data is stored
server side.</p>
<h3>Expiration is more difficult</h3>
<p>The downside to using client-side storage is that there is no way, short of the
expiration on the cookie itself for the website to expire a session. There are
work-arounds, but they all require storing state server-side. A hybrid approach
for example is possible, store a unique ID along with the session data, and
store that unique ID server side, but none of the extra data. Remove the unique
ID server side and if we receive a session that contains a unique ID we don't
recognise, we simply clear the session.</p>
<h1>Expiration, why do I care?</h1>
<p>Being able to easily expire a users sessions allows for extra security
measures. For example in Google Mail it is possible to sign out all other
locations, this forces those other locations to re-authenticate before gaining
access to your account.</p>
<p>This is a good security measure to have, so that if a users cookie is stolen,
or their credentials are compromised upon changing their password all their
sessions are invalidated and an attacker using an old cookie/session ID can't
continue to wreak havoc on the users account.</p>
<h1>Cookie format</h1>
<p>If we are just storing a session ID, or the full session the cookie should be
hardened so that it can not be tampered with by a client. Even if you are
protecting the cookie using SSL, we still don't want to allow a malicious user
to modify the cookie to change the session ID or the session itself.</p>
<h2>Signing your cookie</h2>
<p>The single best way to make sure your cookie has not been tampered with is to
cryptographically sign your cookie, and upon receiving the cookie from the
client verifying that the signature matches what you are expecting. This is
especially important if you are using client-side storage, because you don't
want someone to be able to change the user ID from 950 to 1 and suddenly
impersonate a different user.</p>
<h3>Use an HMAC</h3>
<p>HMAC (Hash-based message authentication code) is an cryptographic construct
that uses a hashing algorithm (SHA-1, SHA-256, SHA-3) to create a MAC (message
authentication code) with a secret key. It is very easy given the secret key
and the original data to create the MAC, but it is very difficult if not
impossible to take the original data, and MAC and get the secret key.</p>
<p>This allows us to do the following:</p>
<div class="codehilite"><pre><span></span>data = &quot;Hello World&quot;
mac = HMAC(data, sha256, &quot;SEEKRIT&quot;)
</pre></div>


<p>Our <code>mac</code> would now be equal to:</p>
<div class="codehilite"><pre><span></span>e655f98cb9b3c02f45576f7906d64b0b7f8731f25a5319c42ca666917aca45a4
</pre></div>


<p>If we now create our cookie as follows:</p>
<div class="codehilite"><pre><span></span>cookie = mac + &quot; &quot; + data
</pre></div>


<p>It would look as follows:</p>
<div class="codehilite"><pre><span></span>cookie = e655f98cb9b3c02f45576f7906d64b0b7f8731f25a5319c42ca666917aca45a4 Hello World
</pre></div>


<p>We can then send that to the client that requested the page. Once the client
visits the next page, their browser will send that same cookie back to use. If
we split the mac from the data, we can then do the following operation:</p>
<div class="codehilite"><pre><span></span>cookie = e655f98cb9b3c02f45576f7906d64b0b7f8731f25a5319c42ca666917aca45a4 Hello World
data = &quot;Hello World&quot;
mac = e655f98cb9b3c02f45576f7906d64b0b7f8731f25a5319c42ca666917aca45a4

mac_verify = HMAC(data, sha256, &quot;SEEKRIT&quot;)

mac_verify == mac
</pre></div>


<p>If and only if <code>mac_verify</code> and <code>mac</code> are the same can we be sure that the
cookie has not been tampered with.</p>
<p>This requires that the client is NEVER aware of what we are using as our secret
key. In the above exmaples that is "SEEKRIT". In your web application you will
be required to make this a configuration variable, and you will have to take
care not to commit that configuration variable to a git repository and upload
it to github (for example).</p>
<h3>Do not use a bare hash algorithm</h3>
<p>Using a bare hash algorithm allows for <a href="http://en.wikipedia.org/wiki/Length_extension_attack">length extension attacks</a> if used
incorrectly, this would allow an attacker to concatenate extra data to the end
of our existing data, modify the "MAC" and the server would accept it.</p>
<p>This construct is thus very dangerous:</p>
<div class="codehilite"><pre><span></span>data = &quot;Hello World&quot;
key = &quot;SEEKRIT&quot;
mac = SHA1(key + data)
</pre></div>


<p>The following construct is still not recommended, but is not nearly as
dangerous:</p>
<div class="codehilite"><pre><span></span>mac = SHA1(data + key)
</pre></div>


<p>Due to the key being last, this is not vulnerable to a length extension attack,
however please don't do this, instead stick to using an HMAC instead.</p>
<h1>Encrypting session data</h1>
<p>When using client-side storage, it may be beneficial to encrypt the data to add
an extra layer of security. Even if encrypting the data you need to continue
using a MAC.</p>
<p>Using just encryption will not protect you against decrypting bad data because
an attacker decided to provide invalid data. Signing the cookie data with a MAC
makes sure that the attacker is not able to mess with the ciphertext.</p>
<h1>What are web frameworks/languages doing by default?</h1>
<p>I am most familiar with the <a href="http://www.pylonsproject.org/projects/pyramid/about">Pylons Project's Pyramid Web Framework</a>, the
default session implementation that is provided by the project is named
<code>SignedCookieSessionFactory</code>, as the name implies this uses a client-side
cookie to store the session data, which is signed using a secret key that is
provided upon instantiation of the factory.</p>
<p><a href="http://flask.pocoo.org/docs/quickstart/#sessions">Flask sessions</a> also uses a signed cookie for client-side session storage.</p>
<p><a href="http://api.rubyonrails.org/classes/ActionDispatch/Session/CookieStore.html">Ruby on Rails</a> uses a signed/encrypted cookie for client-side session
storage by default.</p>
<p><a href="http://php.net">PHP</a> does not by default sign the session cookie, it does however use
server-side storage for session data by default. However extra security can be
added by installing <a href="http://www.hardened-php.net/suhosin.127.html">PHP SuHoSin</a> which adds session cookie
encryption/signing.</p>]]></content:encoded>
    </item>
    <item>
      <title>Building custom ports with Poudriere and Portshaker</title>
      <link>http://funcptr.net/2013/12/11/building-custom-ports-with-poudriere-and-portshaker</link>
      <pubDate>Wed, 11 Dec 2013 00:10:11 MST</pubDate>
      <category><![CDATA[poudriere]]></category>
      <category><![CDATA[ports]]></category>
      <category><![CDATA[FreeBSD]]></category>
      <category><![CDATA[portshaker]]></category>
      <guid isPermaLink="true">http://funcptr.net/2013/12/11/building-custom-ports-with-poudriere-and-portshaker</guid>
      <description>Building custom ports with Poudriere and Portshaker</description>
      <content:encoded><![CDATA[<p><em>Guest post by <a href="https://github.com/SirScott">Scott Sturdivant</a>.</em></p>
<p>Maintaining custom ports and integrating them into your build process doesn't
need to be difficult.  The documentation surrounding this process however is
either non-existent, or lacking in its clarity.  At the end of the day, it really
is as simple as maintaining a repository whose structure matches the ports tree
layout, then managing that repository and the standard ports tree
with <a href="http://www.freshports.org/ports-mgmt/portshaker/">portshaker</a>, and finally handing the end result off to <a href="https://fossil.etoilebsd.net/poudriere/doc/trunk/doc/index.wiki">poudriere</a>.</p>
<h1>Your Custom Repository</h1>
<p>For this example, we'll assume a <a href="http://git-scm.com">git</a> repo is used and that you're already
familiar with how to build <a href="http://www.freebsd.org/">FreeBSD</a> <a href="http://www.freebsd.org/ports/">ports</a>.  We'll also assume that we
have but a single port that we're maintaining and that it is called <code>myport</code>.
The hierarchy of your repo should simply be <code>category/myport</code>.  We'll refer to
this repo simply as <code>myrepo</code>.</p>
<h1>Portshaker</h1>
<p>Portshaker is the tool responsible for taking multiple ports sources and then
merging them down into a single target.  In our case, we have two sources: our
git repo (<code>myrepo</code>) containing <code>myport</code>, and the standard FreeBSD ports tree.
We aim to merge this down into a single ports tree that poudriere will then use
for its builds.</p>
<p>To configure portshaker, add the following to the
<code>/usr/local/etc/portshaker.conf</code> file:</p>
<div class="codehilite"><pre><span></span># vim:set syntax=sh:
# $Id: portshaker.conf.sample 116 2008-09-30 16:15:02Z romain.tartiere $

#---[ Base directory for mirrored Ports Trees ]---
mirror_base_dir=&quot;/var/cache/portshaker&quot;

#---[ Directories where to merge ports ]---
ports_trees=&quot;default&quot;

use_zfs=&quot;no&quot;
poudriere_ports_mountpoint=&quot;/usr/local/poudriere/ports&quot;
default_poudriere_tree=&quot;default&quot;
default_merge_from=&quot;freebsd myrepo&quot;
</pre></div>


<p>Some key points here are that the two items listed in for the
<code>default_merge_from</code> argument need to have scripts present in the
<code>/usr/local/etc/portshaker.d</code> directory.  Further more, the combination of the
<code>poudriere_ports_mountpoint</code> and <code>default_poudriere_tree</code> needs to be a ports
tree that is then registered with poudriere.</p>
<p>Next, we need to tell portshaker how to go off and fetch our two types of ports
trees, <code>freebsd</code> and <code>myrepo</code>.  For the <code>freebsd</code> ports tree, create
<code>/usr/local/etc/portshaker.d/freebsd</code> with the following contents and make it
executable:</p>
<div class="codehilite"><pre><span></span><span class="ch">#!/bin/sh</span>
. /usr/local/share/portshaker/portshaker.subr
<span class="nv">method</span><span class="o">=</span><span class="s2">&quot;portsnap&quot;</span>
run_portshaker_command <span class="nv">$*</span>
</pre></div>


<p>Next, create a similar script to handle our repository containing our custom
port.  <code>/usr/local/etc/portshaker.d/myrepo</code> should contain the following and
similarly be executable:</p>
<div class="codehilite"><pre><span></span><span class="ch">#!/bin/sh</span>
. /usr/local/share/portshaker/portshaker.subr
<span class="nv">method</span><span class="o">=</span><span class="s2">&quot;git&quot;</span>
<span class="nv">git_clone_uri</span><span class="o">=</span><span class="s2">&quot;http://github.com/scott.sturdivant/packaging.git&quot;</span>
<span class="nv">git_branch</span><span class="o">=</span><span class="s2">&quot;master&quot;</span>
run_portshaker_command <span class="nv">$*</span>
</pre></div>


<p>Obviously replace the <code>git_clone_uri</code> and <code>git_branch</code> variables to reflect
your actual configuration.  For more information about the values and what they
can contain, consult <code>man portshaker.d</code></p>
<p>Now, portshaker should be all set.  Execute <code>portshaker -U</code> to update your
<code>merge_from</code> ports trees (<code>freebsd</code> and <code>myrepo</code>).  You'll see the standard
<a href="http://www.freebsd.org/cgi/man.cgi?query=portsnap&amp;sektion=8">portsnap</a> fetch and extract process as well as a git clone.  After a good
bit of time, these will both be present in the <code>/var/cache/portshaker</code>
directory.  Go ahead and merge them together by executing <code>portshaker -M</code>.</p>
<p>Hooray!  You now have <code>/usr/local/poudriere/ports/default/ports</code> that is a
combination of the normal ports tree and your custom one.</p>
<p>We're effectively complete with configuring portshaker.  Whenever your port is
updated, just re-run <code>portshaker -U</code> and <code>portshaker -M</code> to grab the latest
changes and perform the merge.</p>
<h1>Poudriere</h1>
<p>Poudriere is a good tool for building ports.  We will use it to handle our
merged directory.  Begin by configuring poudriere
(<code>/usr/local/etc/poudriere.conf</code>):</p>
<div class="codehilite"><pre><span></span>NO_ZFS=yes
FREEBSD_HOST=ftp://ftp.freebsd.org
RESOLV_CONF=/etc/resolv.conf
BASEFS=/usr/local/poudriere
USE_PORTLINT=no
USE_TMPFS=yes
DISTFILES_CACHE=/usr/ports/distfiles
CHECK_CHANGED_OPTIONS=yes
</pre></div>


<p>Really there's nothing here that is specific to the problem at hand, so feel
free to consult the provided configuration file to tune it to your needs.</p>
<p>Now, the step that <em>is</em> specific is to set poudriere up with a ports tree that
it does not manage, specifically our resultant merged directory.  If you
consult <code>man poudriere</code>, it specifies that for the <code>ports</code> subcommand, there is
a <code>-m method</code> switch which controls the methodology used to create the ports
tree.  By default, it is portsnap.  This is confusing as in our case, we do not
want poudriere to actually do anything.  We want it to just use an existing
path.  Fortunately, there is a way!</p>
<p>The poudriere wiki has an entry for using the <a href="https://fossil.etoilebsd.net/poudriere/doc/trunk/doc/use_system_ports_tree.wiki">system ports tree</a>, so we
adopt it for our needs by executing:</p>
<div class="codehilite"><pre><span></span>poudriere ports -c -F -f none -M /usr/local/poudriere/ports/default \
-p default
</pre></div>


<p>If you've consulted the poudriere manpage, you'll see that the <code>-F</code> and <code>-f</code>
switches both reference ZFS in their help.  As we're not using ZFS, it's not
clear how they will behave.  However, in conjunction with the custom mountpoint
(<code>-M /usr/local/poudriere/ports/default</code>), we ultimately wind up with what we
want, a ports tree that poudriere can use, but does not manage:</p>
<div class="codehilite"><pre><span></span># poudriere ports -l
PORTSTREE            METHOD     PATH
default              -          /usr/local/poudriere/ports/default
</pre></div>


<p>Note that this resulting PATH is the combination of the
<code>poudriere_ports_mountpoint</code> and <code>default_poudriere_tree</code> variables present in
our <code>/usr/local/etc/portshaker.conf</code> configuration file.</p>
<h1>Building software from your custom ports tree</h1>
<p>Go ahead and create your jail(s) like you normally would (i.e.
<code>poudriere -c -j 92amd64 -V 9.2-RELEASE -a amd64</code>) and any other configuration
you would like, and then go ahead and build <code>myport</code> with
<code>poudriere bulk -j 92amd64 -p default category/myport</code>.  Success!</p>]]></content:encoded>
    </item>
    <item>
      <title>User sessions, what data should be stored where?</title>
      <link>http://funcptr.net/2013/08/25/user-sessions,-what-data-should-be-stored-where-</link>
      <pubDate>Sun, 25 Aug 2013 13:05:00 MDT</pubDate>
      <category><![CDATA[access control]]></category>
      <category><![CDATA[secure programming]]></category>
      <category><![CDATA[good practices]]></category>
      <category><![CDATA[sessions]]></category>
      <guid isPermaLink="true">http://funcptr.net/2013/08/25/user-sessions,-what-data-should-be-stored-where-</guid>
      <description>User sessions, what data should be stored where?</description>
      <content:encoded><![CDATA[<p><em>This article has been updated. For the old version please check
<a href="https://web.archive.org/web/20130826031727/http://funcptr.net/2013/08/25/user-sessions,-what-data-should-be-stored-where-/">Archive.org</a></em></p>
<p>A couple of days ago on <a href="http://reddit.com/">reddit.com</a>'s <a href="http://reddit.com/r/netsec/">/r/netsec</a> a poster by the name
of <a href="http://danweber.blogspot.com/">Dan Weber</a> posted what he believed to be an attack on PHP sessions:
<a href="http://danweber.blogspot.com/2013/08/hacking-php-sessions-by-running-out-of.html">Hacking PHP sessions by running out of memory</a> <a href="http://www.reddit.com/r/netsec/comments/1k7khy/i_know_its_not_that_hard_but_i_think_i_found_a/">(reddit link)</a>. The way
the "attack" works is as follows:</p>
<ol>
<li>Create a new session</li>
<li>Assign some new data to the session, in this case a username which is used
     to signify that the user is logged in.</li>
<li>Check to verify that the user should be logged in</li>
<li>If the user should not be logged in, destroy the session.</li>
</ol>
<p>The "attack" would be to run the <a href="http://php.net/">PHP</a> script out of memory on number 3, since
once something is set on the session it is immediately stored, so even if the
user is not supposed to be logged in, they are now logged in since their
session says they are.</p>
<p>I wouldn't necessarily call this a PHP hack, this is really just bad practice
in terms of programming, the logic should be reversed.</p>
<ol>
<li>Create a new session</li>
<li>Verify the user should be logged in</li>
<li>If so, set the session data</li>
<li>If the user should not be logged in, don't set the session data</li>
</ol>
<p>That would solve the problem at hand, and now there is no way for the user to
trick the PHP script into believing she is logged in when that is not the case.</p>
<p>However as the discussion went on on Reddit, it became even more clear that
there are no good resources on what you should store in the user session, and
what you shouldn't store in the user session. Some of these things may seem
like common knowledge, but sadly this is something every single new person to
programming has to learn on their own.</p>
<h1>Some assumptions</h1>
<p>Let's get this out of the way, this is in no way limited to PHP, but it is the
one I will be using as an example. This can all apply to Ruby (<a href="http://rubyonrails.org">Ruby on
Rails</a>), Python (<a href="http://www.pylonsproject.org">Pyramid</a>) or many other frameworks.</p>
<p>The basic problem is that generally writing to the session is not an atomic
transaction based on the page accessed, so the assumption made in this article
is that when you write to the session it is instantly committed, and there is
no way to roll it back upon failure. If there was, our first example listed
wouldn't be able to occur since upon running out of memory at step 3, the
session would have been rolled back and cleaned up.</p>
<h1>What should you store in the users session?</h1>
<p>You should only really store anything in the session that if it were made
public it would do very little harm.</p>
<p>Really the list of items to be stored in a session are as follows:</p>
<ol>
<li>The users unique id (The ID that allows you to retrieve the users
     information from storage)</li>
<li>Temporary state (i.e. Flash messages)</li>
<li>CSRF token</li>
</ol>
<p>More importantly, don't store permission bits, or group memberships, or
anything that is used to allow/deny access to particular resources. You want to
store just enough information that upon a user accessing your site you are able
to retrieve the users information from storage, and based upon that information
from storage you then make decisions such as permissions/group memberships.</p>
<h2>Why is this important?</h2>
<p>One of the things that Dan Weber brought up in the Reddit post was storing the
users permission level and group membership in the session. If your code is
then relying on the session to always contain the right permission level, then
there is no way to expire someones access to the data.</p>
<ol>
<li>A user logs in</li>
<li>A user gets various permissions, and they are set in the session</li>
<li>The user has been fired from his job, and an administrator removes the
     users permissions</li>
<li>Since the user is still logged in, the users permission bits are still
     set, and he continues to have access to parts of the site/information he
     shouldn't have access to.</li>
</ol>
<p>If instead on every page visit we simply pull out the users unique id and
verify the permissions upon access, as soon as the permissions are revoked by
the administrator the user no longer has access to the various resources.</p>
<h2>Temporary state</h2>
<p>There has to be an easy way to remember something from page visit to page visit
that isn't considered detrimental if the information gets lost. One of those
things is flash messages. Flash messages are generally used to provide the user
indication that something has changed, they are shown once and then never
again.</p>
<p>Storing these as session data makes sense. If the flash message gets set,
great, if it doesn't get set, it doesn't matter. Flash messages are simply a
notification tool, if the user misses them it isn't important.</p>
<h1>What you shouldn't store in the session</h1>
<p>Definitely don't store any kind of permission bits, groups a user is a part of
or anything that would allow the user access that they normally would not be
able to access.</p>
<p>On each page access check what permissions the user has. While it may mean a
little more heavy lifting server side it provides extra security, and the means
to enforce changes in permissions instantly.</p>
<h1>Good secure programming practices</h1>
<p>Keep secure programming practices in mind at all times, always consider how the
information you are storing/processing is accessed/viewed/administered. More
importantly think about the access controls that are in place, and how one
could expire access to a particular resource without requiring a co-operative
client.</p>
<p>The ordering of how variables are set, and when they are set are very
important.  <code>$_SESSION['isadmin'] = True</code> at the top of a PHP script, and then
removing it by checking to see if the user is actually an administrator later
on in the script is a bad idea.</p>
<p>Always order your code so that if a failure does occur there is no chance that
a critical section of your code is executed by accident, or that information is
stored in a half-verified state. This is especially important for access
control.</p>]]></content:encoded>
    </item>
  </channel>
</rss>
