DEV Community

Stefor07
Stefor07

Posted on

How I Got OpenStack Keystone Running on Windows: A Collection of Hacks

Introduction

Some projects are born from a simple question: "Will it work?"

This is one of them. OpenStack Keystone — the OpenStack cloud identity and authentication service — was designed, developed, and tested exclusively on Linux. The official documentation doesn't even mention Windows to advise against it. It simply doesn't exist in that context.

And yet, here we are.

What initially seemed like a curious experiment turned into a journey through the bowels of Python, the hidden assumptions of open source software, and the vast gulf separating Linux from Windows beneath the surface.

What we discovered

The first lesson was immediate: Keystone doesn't just use Python — it uses Linux. Not explicitly, not with clear warnings, but silently, through dozens of dependencies that assume it runs on a POSIX system. Modules like grp, pwd, fcntl, termios, resource — all taken for granted, all nonexistent on Windows. Every failed import was a window into how deeply open source software is ingrained in the Unix ecosystem.

The second lesson was about versions. Python 3.14 was too new — ssl.wrap_socket had already been removed. Cygwin seemed like a shortcut but proved to be a dead end when it came to compiling libraries with native dependencies like cryptography, which in recent versions requires Rust. Native Python 3.11 on Windows was the right balance.

The third lesson, perhaps the most interesting, was discovering where the line between configuration and code ends. Typically, you configure an application by editing .conf files or environment variables. Here, however, we found ourselves writing stubs of entire modules, patching third-party library sources, and completely replacing files like pipe_mutex.py because it imported eventlet.asyncio — removed in recent versions of eventlet. The line between "user" and "developer" disappeared completely.

The paradox of this project

There's something deliberately absurd about all this. Keystone is the simplest component of OpenStack — it only handles authentication and authorization. Subsequent services — Nova for compute, Neutron for networking, Cinder for storage — depend on even deeper Linux features: KVM, libvirt, Open vSwitch, network namespaces, LVM. Running them on Windows isn't difficult: it's impossible by design.

So why do it?

Because the process of attempting the impossible teaches more than the process of doing the obvious. Every mistake revealed something: about how Python handles modules, about how open-source libraries assume their environment, about how software that seems portable often isn't as soon as you scratch the surface.

And finally, Keystone runs. On Windows. With MariaDB. It responds to REST requests, issues tokens, and authenticates users. It's not production-ready, it's not supported, it's not sensible — but it works.

Sometimes that's all it takes.


Prerequisites

Before proceeding, install the following:


Step 1 — Configure MariaDB

Open C:\Program Files\MariaDB xx.x\data\my.ini and add under [mysqld]:

[mysqld]
character-set-server = utf8mb4
collation-server     = utf8mb4_unicode_ci
max_connections      = 200
Enter fullscreen mode Exit fullscreen mode

Restart the service:

net stop mariadb
net start mariadb
Enter fullscreen mode Exit fullscreen mode

Step 2 — Create the Keystone Database

Open the MariaDB client:

cd "C:\Program Files\MariaDB xx.x\bin"
mysql -u root
Enter fullscreen mode Exit fullscreen mode

Then run:

CREATE DATABASE keystone CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'keystone'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'localhost';
FLUSH PRIVILEGES;
Enter fullscreen mode Exit fullscreen mode

Step 3 — Install Python Packages

pip install pymysql cryptography waitress eventlet keystone python-openstackclient
Enter fullscreen mode Exit fullscreen mode

On native Windows, cryptography installs from prebuilt wheels — no Rust needed. This is one of the key reasons to avoid Cygwin for this setup.


Step 4 — Create the Keystone Configuration

mkdir C:\keystone
mkdir C:\keystone\fernet-keys
mkdir C:\keystone\credential-keys
Enter fullscreen mode Exit fullscreen mode

Create C:\keystone\keystone.conf:

[DEFAULT]
log_file = C:/keystone/keystone.log

[database]
connection = mysql+pymysql://keystone:your_password@127.0.0.1/keystone

[token]
provider = fernet

[fernet_tokens]
key_repository = C:/keystone/fernet-keys

[credential]
key_repository = C:/keystone/credential-keys
Enter fullscreen mode Exit fullscreen mode

⚠️ The key_repository values will be ignored by Keystone due to hardcoded internal defaults. We will patch this in the next step.


Step 5 — Patch Keystone for Windows

This is the heart of the guide. Keystone assumes a POSIX environment throughout its codebase. We need to fix this systematically.

Your site-packages folder is at:

C:\Users\<username>\AppData\Local\Programs\Python\Python311\Lib\site-packages\
Enter fullscreen mode Exit fullscreen mode

5a. Create missing Unix module stubs

Create each of these files directly in site-packages\:

grp.py

class struct_group:
    def __init__(self, name, passwd, gid, mem):
        self.gr_name = name; self.gr_passwd = passwd
        self.gr_gid = gid; self.gr_mem = mem

def getgrnam(name): return struct_group(name, '', 0, [])
def getgrgid(gid): return struct_group('users', '', gid, [])
def getgrall(): return [struct_group('users', '', 0, [])]
Enter fullscreen mode Exit fullscreen mode

pwd.py

class struct_passwd:
    def __init__(self, name, uid, gid, dir):
        self.pw_name = name; self.pw_passwd = ''
        self.pw_uid = uid; self.pw_gid = gid
        self.pw_gecos = ''; self.pw_dir = dir; self.pw_shell = ''

def getpwnam(name):
    import os
    return struct_passwd(name, 0, 0, os.path.expanduser('~'))

def getpwuid(uid):
    import os
    return struct_passwd('username', uid, 0, os.path.expanduser('~'))

def getpwall(): return [getpwuid(0)]
Enter fullscreen mode Exit fullscreen mode

fcntl.py

import struct
LOCK_SH=1; LOCK_EX=2; LOCK_NB=4; LOCK_UN=8
F_GETFD=1; F_SETFD=2; F_GETFL=3; F_SETFL=4

def fcntl(fd, cmd, arg=0): return 0
def ioctl(fd, request, arg=0, mutate_flag=True):
    if isinstance(arg, bytes) and len(arg) == 8:
        return struct.pack("HHHH", 24, 80, 0, 0)
    return 0
def flock(fd, operation): return 0
def lockf(fd, cmd, length=0, start=0, whence=0): return 0
Enter fullscreen mode Exit fullscreen mode

termios.py

TCSANOW=0; TCSADRAIN=1; TCSAFLUSH=2; TIOCGWINSZ=0x5413

def tcgetattr(fd): return [0,0,0,0,0,0,[]]
def tcsetattr(fd, when, attrs): pass
def tcdrain(fd): pass
def tcflush(fd, queue): pass
def tcflow(fd, action): pass
Enter fullscreen mode Exit fullscreen mode

resource.py

import mmap
RLIMIT_NOFILE=7; RLIMIT_CPU=0; RLIMIT_AS=9; RLIM_INFINITY=float('inf')

def getrlimit(resource): return (1024, 4096)
def setrlimit(resource, limits): pass
def getpagesize(): return mmap.PAGESIZE
def getrusage(who):
    return type('r',(),{k:0 for k in [
        'ru_utime','ru_stime','ru_maxrss','ru_ixrss','ru_idrss','ru_isrss',
        'ru_minflt','ru_majflt','ru_nswap','ru_inblock','ru_oublock',
        'ru_msgsnd','ru_msgrcv','ru_nsignals','ru_nvcsw','ru_nivcsw']})()
Enter fullscreen mode Exit fullscreen mode

sitecustomize.py — loaded automatically by Python at every startup:

import os

if not hasattr(os, 'geteuid'):  os.geteuid  = lambda: 0
if not hasattr(os, 'getuid'):   os.getuid   = lambda: 0
if not hasattr(os, 'getgid'):   os.getgid   = lambda: 0
if not hasattr(os, 'getegid'):  os.getegid  = lambda: 0
if not hasattr(os, 'getgroups'):os.getgroups= lambda: [0]
if not hasattr(os, 'getlogin'): os.getlogin = lambda: os.environ.get('USERNAME','admin')
if not hasattr(os, 'setegid'):  os.setegid  = lambda gid: None
if not hasattr(os, 'seteuid'):  os.seteuid  = lambda uid: None
if not hasattr(os, 'setgid'):   os.setgid   = lambda gid: None
if not hasattr(os, 'setuid'):   os.setuid   = lambda uid: None
if not hasattr(os, 'setgroups'):os.setgroups= lambda groups: None
if not hasattr(os, 'chown'):    os.chown    = lambda path, uid, gid: None
Enter fullscreen mode Exit fullscreen mode

5b. Patch existing source files

keystone\cmd\cli.py — find and replace:

# Before
running_as_root = os.geteuid() == 0
# After
running_as_root = False
Enter fullscreen mode Exit fullscreen mode

oslo_log\pipe_mutex.py — replace the entire file content:

import threading

class PipeMutex:
    def __init__(self):
        self._lock = threading.Lock()
        self.owner = None; self.count = 0

    def acquire(self, blocking=True):
        if self.owner == threading.current_thread():
            self.count += 1; return True
        result = self._lock.acquire(blocking)
        if result:
            self.owner = threading.current_thread(); self.count = 1
        return result

    def release(self):
        if self.owner != threading.current_thread():
            raise RuntimeError("release() called from wrong thread")
        self.count -= 1
        if self.count == 0:
            self.owner = None; self._lock.release()

    def __enter__(self): self.acquire(); return self
    def __exit__(self, *args): self.release()

def pipe_createLock(self):
    self.lock = PipeMutex()
Enter fullscreen mode Exit fullscreen mode

5c. Patch hardcoded /etc/keystone paths

Keystone has paths like /etc/keystone/fernet-keys/ hardcoded as defaults in its config options. Find them:

findstr /r /s "etc.keystone" C:\Users\PC\AppData\Local\Programs\Python\Python311\Lib\site-packages\keystone\*.py
Enter fullscreen mode Exit fullscreen mode

In each file found, replace:

# Before
default='/etc/keystone/fernet-keys/'
default='/etc/keystone/credential-keys/'

# After
default='C:\\keystone\\fernet-keys\\'
default='C:\\keystone\\credential-keys\\'
Enter fullscreen mode Exit fullscreen mode

Step 6 — Set the Config Environment Variable

Make it permanent so every new cmd session picks it up:

setx OS_KEYSTONE_CONFIG_FILES "C:\keystone\keystone.conf"
Enter fullscreen mode Exit fullscreen mode

Close and reopen cmd after this command.


Step 7 — Initialize the Database

keystone-manage --config-file C:\keystone\keystone.conf db_sync
Enter fullscreen mode Exit fullscreen mode

Check C:\keystone\keystone.log to confirm the migration completed successfully.


Step 8 — Generate Fernet and Credential Keys

First try the standard commands:

keystone-manage --config-file C:\keystone\keystone.conf fernet_setup --keystone-user %USERNAME% --keystone-group Users

keystone-manage --config-file C:\keystone\keystone.conf credential_setup --keystone-user %USERNAME% --keystone-group Users
Enter fullscreen mode Exit fullscreen mode

If the folders remain empty (common on Windows), generate the keys directly with Python:

# C:\keystone\gen_keys.py
from cryptography.fernet import Fernet
import os

for d in ['C:/keystone/fernet-keys', 'C:/keystone/credential-keys']:
    os.makedirs(d, exist_ok=True)
    for i in [0, 1]:
        with open(f'{d}/{i}', 'wb') as f:
            f.write(Fernet.generate_key())
        print(f'Created: {d}/{i}')

print('Done!')
Enter fullscreen mode Exit fullscreen mode
python C:\keystone\gen_keys.py
Enter fullscreen mode Exit fullscreen mode

Step 9 — Bootstrap Keystone

keystone-manage --config-file C:\keystone\keystone.conf bootstrap ^
  --bootstrap-password adminpass ^
  --bootstrap-admin-url http://localhost:5000/v3/ ^
  --bootstrap-internal-url http://localhost:5000/v3/ ^
  --bootstrap-public-url http://localhost:5000/v3/ ^
  --bootstrap-region-id RegionOne
Enter fullscreen mode Exit fullscreen mode

Replace adminpass with a strong password of your choice.


Step 10 — Create the Startup Script

Create C:\keystone\start_keystone.py:

import os

# Must be set BEFORE any keystone import
os.environ["OS_KEYSTONE_CONFIG_FILES"] = "C:/keystone/keystone.conf"

import eventlet
eventlet.monkey_patch(os=False)

from waitress import serve
from keystone.server.wsgi import initialize_public_application

app = initialize_public_application()
print("Keystone listening on http://0.0.0.0:5000")
serve(app, host="0.0.0.0", port=5000, threads=8)
Enter fullscreen mode Exit fullscreen mode

Start it:

python C:\keystone\start_keystone.py
Enter fullscreen mode Exit fullscreen mode

You should see:

Keystone listening on http://0.0.0.0:5000
Enter fullscreen mode Exit fullscreen mode

Step 11 — Test Keystone

Open a new cmd window and run:

set OS_USERNAME=admin
set OS_PASSWORD=adminpass
set OS_PROJECT_NAME=admin
set OS_USER_DOMAIN_ID=default
set OS_PROJECT_DOMAIN_ID=default
set OS_AUTH_URL=http://localhost:5000/v3
set OS_IDENTITY_API_VERSION=3

openstack token issue
Enter fullscreen mode Exit fullscreen mode

A successful response will return a valid token — Keystone is working.

You can also verify via browser or curl: http://localhost:5000/v3


Step 12 — Run as a Windows Service (Optional)

To start Keystone automatically at boot without running the script manually, use NSSM (Non-Sucking Service Manager).

Install NSSM

curl -L https://nssm.cc/release/nssm-2.24.zip -o C:\nssm.zip
tar -xf C:\nssm.zip -C C:\
Enter fullscreen mode Exit fullscreen mode

Register the service

C:\nssm-2.24\win64\nssm.exe install KeystoneService "C:\Users\<username>\AppData\Local\Programs\Python\Python311\python.exe"
C:\nssm-2.24\win64\nssm.exe set KeystoneService AppParameters "C:\keystone\start_keystone.py"
C:\nssm-2.24\win64\nssm.exe set KeystoneService AppDirectory "C:\keystone"
C:\nssm-2.24\win64\nssm.exe set KeystoneService DisplayName "OpenStack Keystone"
C:\nssm-2.24\win64\nssm.exe set KeystoneService Description "OpenStack Identity Service"
C:\nssm-2.24\win64\nssm.exe set KeystoneService Start SERVICE_AUTO_START
C:\nssm-2.24\win64\nssm.exe set KeystoneService AppEnvironmentExtra "OS_KEYSTONE_CONFIG_FILES=C:\keystone\keystone.conf"
C:\nssm-2.24\win64\nssm.exe set KeystoneService AppStdout "C:\keystone\service.log"
C:\nssm-2.24\win64\nssm.exe set KeystoneService AppStderr "C:\keystone\service_error.log"
Enter fullscreen mode Exit fullscreen mode

Manage the service

C:\nssm-2.24\win64\nssm.exe start KeystoneService
C:\nssm-2.24\win64\nssm.exe stop KeystoneService
C:\nssm-2.24\win64\nssm.exe restart KeystoneService
C:\nssm-2.24\win64\nssm.exe status KeystoneService
C:\nssm-2.24\win64\nssm.exe remove KeystoneService confirm
Enter fullscreen mode Exit fullscreen mode

Summary of All Hacks Applied

Problem Solution
Python 3.12+ removes ssl.wrap_socket Use Python 3.11
grp, pwd don't exist on Windows Create Python stubs
fcntl, termios, resource missing Create Python stubs
os.geteuid, os.setegid etc. missing sitecustomize.py with stubs
oslo_log.pipe_mutex imports eventlet.asyncio Replace with threading-based version
fernet_setup ignores config on Windows Generate keys manually with Python
gunicorn not supported on Windows Use waitress instead
mysqlclient won't compile Use pure-Python PyMySQL
Hardcoded /etc/keystone paths Patch source defaults directly
cryptography requires Rust on Cygwin Use native Windows Python (prebuilt wheels)

Final Thoughts

This setup works — but it's a house of cards. Every pip update, every Keystone version bump, every eventlet release could silently break one of these patches. The correct answer for running OpenStack on Windows is WSL2 or a Linux VM with DevStack.

But if you want to understand what OpenStack actually does under the hood, wrestling with it on an unsupported platform is one of the best ways to learn. You'll never take grp.getgrnam() for granted again.


Have questions or found additional patches needed? Drop them in the comments.

Top comments (0)