Skip to content

Commit 6175588

Browse files
DirectXMan12frozencemetery
authored andcommitted
Docs: Add in the basic tutorial
This commit adds the basic tutorial to the documentation. It is converted into a Sphinx-readable (docutils) format using the `recommonmark` package.
1 parent 691e288 commit 6175588

File tree

7 files changed

+249
-1
lines changed

7 files changed

+249
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ docs/build
1414
__dont_use_cython__.txt
1515
**/__pycache__
1616
.eggs
17+
.venv

docs-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
Sphinx >= 1.3.1
22
sphinx-rtd-theme >= 0.1.9
33
sphinxcontrib-napoleon >= 0.2.8
4+
recommonmark >= 0.4.0
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from recommonmark.parser import CommonMarkParser
2+
from docutils import nodes
3+
4+
# treats "verbatim" (code without a language specified) as a code sample
5+
class AllCodeCommonMarkParser(CommonMarkParser):
6+
def verbatim(self, text):
7+
node = nodes.literal_block(text, text)
8+
self.current_node.append(node)

docs/source/basic-tutorial.md

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
A Basic Introduction to GSSAPI
2+
==============================
3+
4+
GSSAPI (which stands for "Generic Security Service API") is an
5+
standard layer for interfacing with security services. While it
6+
supports multiple different mechanisms, it is most commonly used
7+
with Kerberos 5 ("krb5" for short).
8+
9+
This tutorial will provide a basic introduction to interacting with
10+
GSSAPI through Python.
11+
12+
*Note*: This file is designed to be runnable using
13+
[YALPT](https://github.com/directxman12/yalpt). You can also just
14+
read it normally.
15+
16+
To start out, we'll import python-gssapi, and save the current FQDN
17+
for later:
18+
19+
>>> import gssapi, socket
20+
>>> FQDN = socket.getfqdn()
21+
>>>
22+
23+
Note that this assumes you have a KRB5 realm set up, and some relevant
24+
functions available in the `REALM` object (see gssapi-console.py, or
25+
try `$ run-lit -e gssapi basic-tutorial.md` when you have both
26+
gssapi-console and yalpt installed). Any actions performed using the
27+
`REALM` object are not part of the GSSAPI library; the `REALM` object
28+
simply contians wrappers to krb5 commands generally run separately from
29+
the application using GSSAPI.
30+
31+
Names and Credentials
32+
---------------------
33+
34+
Two important concepts in GSSAPI are *names* and *credentials*.
35+
36+
*Names*, as the name suggests, identify different entities, be they
37+
users or services. GSSAPI has the concept of different *name types*.
38+
These represent different types of names and corresponding sytaxes
39+
for representing names as strings.
40+
41+
Suppose we wanted to refer to an HTTP server on the current host.
42+
We could refer to it as a *host-based service*, or in the default
43+
mechanism form (in this case, for krb5):
44+
45+
>>> server_hostbased_name = gssapi.Name('http@' + FQDN, name_type=gssapi.NameType.hostbased_service)
46+
>>> server_hostbased_name
47+
Name(b'http@sross', <OID 1.2.840.113554.1.2.1.4>)
48+
>>> server_name = gssapi.Name('http/sross@')
49+
>>> server_name
50+
Name(b'http/sross@', None)
51+
>>>
52+
53+
These are both effectively the same, but if we *canonicalize* both
54+
names with respect to krb5, we'll see that GSSAPI knows they're the
55+
same:
56+
57+
>>> server_name == server_hostbased_name
58+
False
59+
>>> server_canon_name = server_name.canonicalize(gssapi.MechType.kerberos)
60+
>>> server_hostbased_canon_name = server_hostbased_name.canonicalize(gssapi.MechType.kerberos)
61+
>>> server_canon_name == server_hostbased_canon_name
62+
True
63+
>>>
64+
65+
To compare two names of different name types, you should canonicalize
66+
them first.
67+
68+
*Credentials* represent identification for a user or service. In
69+
order to establish secure communication with other entities, a user
70+
or service first needs credentials. For the krb5 mechanism,
71+
credentials generally represent a handle to the TGT.
72+
73+
Credentials may be acquired for a particular name, or the default set
74+
of credentials may be acquired.
75+
76+
For instance, suppose that we are writing a server, and wish to
77+
communicate accept connections as the 'http' service. We would need
78+
to acquire credentials as such:
79+
80+
>>> REALM.addprinc('http/%s@%s' % (FQDN, REALM.realm))
81+
>>> REALM.extract_keytab('http/%s@%s' % (FQDN, REALM.realm), REALM.keytab)
82+
>>> server_creds = gssapi.Credentials(usage='accept', name=server_name)
83+
>>>
84+
85+
Note that for the krb5 mechanism, in order to acquire credentials with
86+
the GSSAPI, the system must already have a way to access those credentials.
87+
For users, this generally means that they have already performed a `kinit`
88+
(i.e. have cached a TGT), while for services (like above), having a keytab
89+
is sufficient. This process is generally performed outside the application
90+
using the GSSAPI.
91+
92+
Credentials have a *usage*: 'accept' for accepting security contexts,
93+
'initiate' for initiating security contexts, or 'both' for
94+
credentials used for both initiating and accepting security contexts.
95+
96+
Credentials also have an associated *name*, *lifetime* (which may
97+
be `None` for indefinite), and set of *mechansims* with which the
98+
credentials are usable:
99+
100+
>>> server_creds.usage
101+
'accept'
102+
>>> server_creds.name == server_name
103+
True
104+
>>> server_creds.lifetime is None
105+
True
106+
>>> gssapi.MechType.kerberos in server_creds.mechs
107+
True
108+
>>> gssapi.MechType.kerberos in server_creds.mechs
109+
True
110+
>>>
111+
112+
Each of these settings is setable from the constructor as `usage`,
113+
`name`, `lifetime`, and `mechs`.
114+
115+
Security Contexts
116+
-----------------
117+
118+
*Security contexts* represent active sessions between two different
119+
entities. Security contexts are used to verify identities, as well
120+
as ensure *integrity* (message signing), *confidentiality* (message
121+
encryption), or both for messages exchanged between the two parties.
122+
123+
When establishing a security context, the default credentials are
124+
used unless otherwise specified. This allows applications to use
125+
the user's already acquired credentials:
126+
127+
>>> client_ctx = gssapi.SecurityContext(name=server_name, usage='initiate')
128+
>>> initial_client_token = client_ctx.step()
129+
>>> client_ctx.complete
130+
False
131+
>>>
132+
133+
Just like credentials, security contexts are either initiating
134+
contexts, or accepting contexts (they cannot be both). Initating
135+
contexts must specify at least a target name. In this case,
136+
we indicate that we wish to establish a context with the HTTP server
137+
from above. The http server can then accept that context:
138+
139+
>>> server_ctx = gssapi.SecurityContext(creds=server_creds, usage='accept')
140+
>>> initial_server_token = server_ctx.step(initial_client_token)
141+
>>>
142+
143+
As you can see, creating an accepting security context is similar.
144+
Here, we specify a set of accepting credentials to use, although
145+
this is optional (the defaults will be used if no credentials are
146+
specified).
147+
148+
Let's finish up the exchange:
149+
150+
>>> server_tok = initial_server_token
151+
>>>
152+
>>> while not (client_ctx.complete and server_ctx.complete):
153+
... client_tok = client_ctx.step(server_tok)
154+
... if not client_tok:
155+
... break
156+
... server_tok = server_ctx.step(client_tok)
157+
...
158+
>>> client_ctx.complete and server_ctx.complete
159+
True
160+
>>>
161+
162+
We can now wrap and unwrap messages, using the `wrap` and `unwrap` methods
163+
on `SecurityContext`:
164+
165+
>>> message = b'some message here'
166+
>>> wrapped_message, msg_encrypted = client_ctx.wrap(message, True)
167+
>>> message not in wrapped_message
168+
True
169+
>>> msg_encrypted
170+
True
171+
>>> server_ctx.unwrap(wrapped_message)
172+
UnwrapResult(message=b'some message here', encrypted=True, qop=0)
173+
>>>
174+
175+
We can use the second parameter to control whether or not we encrypt the
176+
messages, or just sign them:
177+
178+
>>> signed_message, msg_encrypted = client_ctx.wrap(message, False)
179+
>>> msg_encrypted
180+
False
181+
>>> message in signed_message
182+
True
183+
>>> server_ctx.unwrap(signed_message)
184+
UnwrapResult(message=b'some message here', encrypted=False, qop=0)
185+
>>>
186+
187+
Manually passing in a second parameter and checking whether or not encryption
188+
was used can get tedious, so python-gssapi provides two convinience methods
189+
to help with this: `encrypt` and `decrypt`. If the context is set up to use
190+
encryption, they will call `wrap` with encryption. If not, they will
191+
call `wrap` without encryption.
192+
193+
>>> encrypted_message = client_ctx.encrypt(message)
194+
>>> encrypted_message != message
195+
True
196+
>>> server_ctx.decrypt(encrypted_message)
197+
b'some message here'
198+
>>>
199+
200+
Notice that if we try to use `decrypt` a signed message, and exception will be raised,
201+
since the context was set up to use encryption (the default):
202+
203+
>>> signed_message, _ = client_ctx.wrap(message, False)
204+
>>> server_ctx.decrypt(signed_message)
205+
Traceback (most recent call last):
206+
File "<stdin>", line 1, in <module>
207+
File "<string>", line 2, in decrypt
208+
File "/home/directxman12/dev/gssapi/gssapi-console/.venv/lib/python3.4/site-packages/gssapi/_utils.py", line 167, in check_last_err
209+
return func(self, *args, **kwargs)
210+
File "/home/directxman12/dev/gssapi/gssapi-console/.venv/lib/python3.4/site-packages/gssapi/sec_contexts.py", line 295, in decrypt
211+
unwrapped_message=res.message)
212+
gssapi.exceptions.EncryptionNotUsed: Confidentiality was requested, but not used: The context was established with encryption, but unwrapped message was not encrypted.
213+
>>>
214+
215+
There you have it: the basics of GSSAPI. You can use the `help` function
216+
at the interpreter, or check the [docs](http://pythonhosted.org/gssapi/)
217+
for more information.

docs/source/conf.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
sys.path.insert(0, os.path.abspath('../..'))
2222
sys.path.insert(0, os.path.abspath('../custom_extensions'))
2323

24+
from custom_recommonmark import AllCodeCommonMarkParser
25+
2426
# -- General configuration -----------------------------------------------------
2527

2628
# If your documentation needs a minimal Sphinx version, state it here.
@@ -33,8 +35,13 @@
3335
# Add any paths that contain templates here, relative to this directory.
3436
templates_path = ['_templates']
3537

38+
# Parsers for different suffixes
39+
source_parsers = {
40+
'.md': AllCodeCommonMarkParser
41+
}
42+
3643
# The suffix of source filenames.
37-
source_suffix = '.rst'
44+
source_suffix = ['.rst', '.md']
3845

3946
# The encoding of source files.
4047
#source_encoding = 'utf-8-sig'

docs/source/index.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@ corresponding C functions. The high-level API makes use of the low-level API
1919
to access underlying GSSAPI functionality. Additionally certain extensions
2020
are currently only available from the low-level API.
2121

22+
To get started, check out the :doc:`tutorials page <tutorials>` or jump
23+
straight into the :doc:`high-level API documentation <gssapi>`.
24+
2225
.. toctree::
2326
:hidden:
2427
:maxdepth: 3
2528

2629
gssapi.rst
2730
gssapi.raw.rst
31+
tutorials.rst
2832

2933

3034
Indices and tables

docs/source/tutorials.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Tutorials
2+
=========
3+
4+
To get started with using Python-GSSAPI, check out some of the following tutorials:
5+
6+
.. toctree::
7+
:maxdepth: 1
8+
9+
basic-tutorial.md
10+

0 commit comments

Comments
 (0)