[PATCH 16/19] encryption: Add <cipher> and <ivgen> to encryption

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



For a luks device, allow the configuration of a specific cipher to be
used for encrypting the volume.

Signed-off-by: John Ferlan <jferlan@xxxxxxxxxx>
---
 docs/formatstorageencryption.html.in               |  78 ++++++++++++-
 docs/schemas/storagecommon.rng                     |  44 ++++++-
 src/conf/domain_conf.c                             |  11 ++
 src/util/virstorageencryption.c                    | 126 +++++++++++++++++++++
 src/util/virstorageencryption.h                    |  13 +++
 .../qemuxml2argv-luks-disk-cipher.xml              |  41 +++++++
 .../qemuxml2xmlout-luks-disk-cipher.xml            |  45 ++++++++
 tests/qemuxml2xmltest.c                            |   1 +
 tests/storagevolxml2xmlin/vol-luks-cipher.xml      |  23 ++++
 tests/storagevolxml2xmlout/vol-luks-cipher.xml     |  23 ++++
 tests/storagevolxml2xmltest.c                      |   1 +
 11 files changed, 402 insertions(+), 4 deletions(-)
 create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml
 create mode 100644 tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml
 create mode 100644 tests/storagevolxml2xmlin/vol-luks-cipher.xml
 create mode 100644 tests/storagevolxml2xmlout/vol-luks-cipher.xml

diff --git a/docs/formatstorageencryption.html.in b/docs/formatstorageencryption.html.in
index ae2e815..dd2f990 100644
--- a/docs/formatstorageencryption.html.in
+++ b/docs/formatstorageencryption.html.in
@@ -71,6 +71,58 @@
       be used as the key to decrypt the volume.
       <span class="since">Since 1.3.6</span>.
     </p>
+    <p>
+      For volume creation, it is possible to specify the encryption
+      algorithm used to encrypt the luks volume. The following two
+      optional elements may be provided for that purpose. It is hypervisor
+      dependent as to which algorithms are supported. The default algorithm
+      for QEMU is 'aes-256-cbc' using 'essiv' for initialization vector
+      generation and 'sha256' hash algorithm for both the master key and
+      the initialization vector generation.
+    </p>
+
+    <dl>
+      <dt><code>cipher</code></dt>
+      <dd>This element describes the cipher algorithm to be used to either
+          encrypt or decrypt the key. This element has the following
+          attributes:
+          <dl>
+            <dt><code>name</code></dt>
+            <dd>The name of the cipher algorithm used for data encryption,
+            such as 'aes', 'des', 'cast5', 'serpent', 'twofish', etc.
+            Support of the specific algorithm is hypervisor dependent.</dd>
+            <dt><code>size</code></dt>
+            <dd>The size of the cipher in bits, such as '256', '192', '128',
+            etc. Support of the specific size for a specific cipher is
+            hypervisor dependent.</dd>
+            <dt><code>mode</code></dt>
+            <dd>An optional cipher algorithm mode such as 'cbc', 'xts',
+            'ecb', etc. Support of the specific cipher mode is
+            hypervisor dependent.</dd>
+            <dt><code>hash</code></dt>
+            <dd>An optional master key hash algorithm such as 'md5', 'sha1',
+            'sha256', etc. Support of the specific hash algorithm is
+            hypervisor dependent.</dd>
+          </dl>
+      </dd>
+      <dt><code>ivgen</code></dt>
+      <dd>This optional element describes the initialization vector
+          generation algorithm used in conjunction with the
+          <code>cipher</code>. If the <code>cipher</code> is not provided,
+          then an error will be generated by the parser.
+          <dl>
+            <dt><code>name</code></dt>
+            <dd>The name of the algorithm, such as 'plain', 'plain64',
+            'essiv', etc. Support of the specific algorithm is hypervisor
+            dependent.</dd>
+            <dt><code>hash</code></dt>
+            <dd>An optional hash algorithm such as 'md5', 'sha1', 'sha256',
+            etc. Support of the specific ivgen hash algorithm is hypervisor
+            dependent.</dd>
+          </dl>
+      </dd>
+    </dl>
+
 
     <h2><a name="example">Examples</a></h2>
 
@@ -84,7 +136,11 @@
       &lt;/encryption&gt;</pre>
 
     <p>
-      Here is a simple example, specifying use of the <code>luks</code> format:
+      Assuming a <a href="formatsecret.html#luksUsageType">
+      <code>luks secret</code></a> is already defined, the following is
+      a simple example specifying use of the <code>luks</code> format
+      for either volume creation without a specific cipher being defined or
+      as part of a domain volume definition:
     </p>
     <pre>
       &lt;encryption format='luks'&gt;
@@ -92,5 +148,25 @@
       &lt;/encryption&gt;
     </pre>
 
+    <p>
+      Here is an example, specifying use of the <code>luks</code> format for
+      a specific cipher algorihm for volume creation:
+    </p>
+    <pre>
+      &lt;volume&gt;
+        &lt;name&gt;twofish.luks&lt;/name&gt;
+        &lt;capacity unit='G'&gt;5&lt;/capacity&gt;
+        &lt;target&gt;
+          &lt;path&gt;/var/lib/libvirt/images/demo.luks&lt;/path&gt;
+          &lt;format type='luks'/&gt;
+          &lt;encryption format='luks'&gt;
+             &lt;secret type='key' usage='luks_example'/&gt;
+             &lt;cipher name='twofish' size='256' mode='cbc' hash='sha256'/&gt;
+             &lt;ivgen name='plain64' hash='sha256'/&gt;
+          &lt;/encryption&gt;
+        &lt;/target&gt;
+      &lt;/volume&gt;
+    </pre>
+
   </body>
 </html>
diff --git a/docs/schemas/storagecommon.rng b/docs/schemas/storagecommon.rng
index 44d4315..b6e8de4 100644
--- a/docs/schemas/storagecommon.rng
+++ b/docs/schemas/storagecommon.rng
@@ -15,9 +15,19 @@
           <value>luks</value>
         </choice>
       </attribute>
-      <zeroOrMore>
-        <ref name='secret'/>
-      </zeroOrMore>
+      <interleave>
+        <zeroOrMore>
+          <ref name='secret'/>
+        </zeroOrMore>
+        <optional>
+          <element name='cipher'>
+            <ref name='keycipher'/>
+          </element>
+          <element name='ivgen'>
+            <ref name='keyivgen'/>
+          </element>
+        </optional>
+      </interleave>
     </element>
   </define>
 
@@ -137,4 +147,32 @@
     </optional>
   </define>
 
+  <define name='keycipher'>
+    <attribute name='name'>
+      <text/>
+    </attribute>
+    <attribute name='size'>
+      <ref name="unsignedInt"/>
+    </attribute>
+    <optional>
+      <attribute name='mode'>
+        <text/>
+      </attribute>
+      <attribute name='hash'>
+        <text/>
+      </attribute>
+    </optional>
+  </define>
+
+  <define name='keyivgen'>
+    <attribute name='name'>
+      <text/>
+    </attribute>
+    <optional>
+      <attribute name='hash'>
+        <text/>
+      </attribute>
+    </optional>
+  </define>
+
 </grammar>
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 10e61da..27f71a5 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -7786,6 +7786,17 @@ virDomainDiskDefParseXML(virDomainXMLOptionPtr xmlopt,
         def->startupPolicy = val;
     }
 
+    if (encryption) {
+        if (encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS &&
+            encryption->cipher.name) {
+
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                           _("supplying the <cipher> for a domain is "
+                             "unnecessary"));
+            goto error;
+        }
+    }
+
     def->dst = target;
     target = NULL;
     def->src->auth = authdef;
diff --git a/src/util/virstorageencryption.c b/src/util/virstorageencryption.c
index 9cca3e8..608a57b 100644
--- a/src/util/virstorageencryption.c
+++ b/src/util/virstorageencryption.c
@@ -35,6 +35,7 @@
 #include "viruuid.h"
 #include "virfile.h"
 #include "virsecret.h"
+#include "virstring.h"
 
 #define VIR_FROM_THIS VIR_FROM_STORAGE
 
@@ -46,6 +47,15 @@ VIR_ENUM_IMPL(virStorageEncryptionFormat,
               "default", "qcow", "luks")
 
 static void
+virStorageEncryptionInfoDefFree(virStorageEncryptionInfoDefPtr def)
+{
+    VIR_FREE(def->name);
+    VIR_FREE(def->mode);
+    VIR_FREE(def->hash);
+}
+
+
+static void
 virStorageEncryptionSecretFree(virStorageEncryptionSecretPtr secret)
 {
     if (!secret)
@@ -63,6 +73,8 @@ virStorageEncryptionFree(virStorageEncryptionPtr enc)
 
     for (i = 0; i < enc->nsecrets; i++)
         virStorageEncryptionSecretFree(enc->secrets[i]);
+    virStorageEncryptionInfoDefFree(&enc->cipher);
+    virStorageEncryptionInfoDefFree(&enc->ivgen);
     VIR_FREE(enc->secrets);
     VIR_FREE(enc);
 }
@@ -80,6 +92,20 @@ virStorageEncryptionSecretCopy(const virStorageEncryptionSecret *src)
     return ret;
 }
 
+
+static int
+virStorageEncryptionInfoDefCopy(const virStorageEncryptionInfoDef *src,
+                                virStorageEncryptionInfoDefPtr dst)
+{
+    if (VIR_STRDUP(dst->name, src->name) < 0 ||
+        VIR_STRDUP(dst->mode, src->mode) < 0 ||
+        VIR_STRDUP(dst->hash, src->hash) < 0)
+        return -1;
+
+    return 0;
+}
+
+
 virStorageEncryptionPtr
 virStorageEncryptionCopy(const virStorageEncryption *src)
 {
@@ -100,6 +126,12 @@ virStorageEncryptionCopy(const virStorageEncryption *src)
             goto error;
     }
 
+    if (virStorageEncryptionInfoDefCopy(&src->cipher, &ret->cipher) < 0)
+        goto error;
+
+    if (virStorageEncryptionInfoDefCopy(&src->ivgen, &ret->ivgen) < 0)
+        goto error;
+
     return ret;
 
  error:
@@ -153,6 +185,51 @@ virStorageEncryptionSecretParse(xmlXPathContextPtr ctxt,
     return NULL;
 }
 
+
+static int
+virStorageEncryptionInfoParse(xmlNodePtr info_node,
+                              virStorageEncryptionInfoDefPtr info,
+                              bool require_size)
+{
+    int ret = -1;
+    char *size_str = NULL;
+
+    if (!(info->name = virXMLPropString(info_node, "name"))) {
+        virReportError(VIR_ERR_XML_ERROR, "%s",
+                       _("missing secret key info name string"));
+        goto cleanup;
+    }
+
+    /* Check for a size string - it's required for cipher, but not for ivgen
+     * if provided for ivgen then just ignore */
+    if (require_size) {
+        if ((size_str = virXMLPropString(info_node, "size")) &&
+            virStrToLong_uip(size_str, NULL, 10, &info->size) < 0) {
+            virReportError(VIR_ERR_XML_ERROR,
+                           _("cannot parse secret key info size string '%s'"),
+                           size_str);
+            goto cleanup;
+        }
+
+        if (!size_str) {
+            virReportError(VIR_ERR_XML_ERROR, "%s",
+                           _("secret key info missing 'size' attribute"));
+            goto cleanup;
+        }
+    }
+
+    /* Optional */
+    info->mode = virXMLPropString(info_node, "mode");
+    info->hash = virXMLPropString(info_node, "hash");
+
+    ret = 0;
+
+ cleanup:
+    VIR_FREE(size_str);
+    return ret;
+}
+
+
 static virStorageEncryptionPtr
 virStorageEncryptionParseXML(xmlXPathContextPtr ctxt)
 {
@@ -196,6 +273,28 @@ virStorageEncryptionParseXML(xmlXPathContextPtr ctxt)
         VIR_FREE(nodes);
     }
 
+    if (ret->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) {
+        xmlNodePtr tmpnode;
+
+        if ((tmpnode = virXPathNode("./cipher[1]", ctxt))) {
+            if (virStorageEncryptionInfoParse(tmpnode, &ret->cipher, true) < 0)
+                goto cleanup;
+        }
+
+        if ((tmpnode = virXPathNode("./ivgen[1]", ctxt))) {
+            /* If no cipher node, then fail */
+            if (!ret->cipher.name) {
+                virReportError(VIR_ERR_XML_ERROR, "%s",
+                                _("missing storage encryption cipher"));
+                goto cleanup;
+            }
+
+            if (virStorageEncryptionInfoParse(tmpnode, &ret->ivgen, false) < 0)
+                goto cleanup;
+        }
+    }
+
+
     return ret;
 
  cleanup:
@@ -250,6 +349,28 @@ virStorageEncryptionSecretFormat(virBufferPtr buf,
     return 0;
 }
 
+
+static void
+virStorageEncryptionInfoDefFormat(virBufferPtr buf,
+                                  const virStorageEncryption *enc)
+{
+    virBufferAsprintf(buf, "<cipher name='%s' size='%u'",
+                      enc->cipher.name, enc->cipher.size);
+    if (enc->cipher.mode)
+        virBufferAsprintf(buf, " mode='%s'", enc->cipher.mode);
+    if (enc->cipher.hash)
+        virBufferAsprintf(buf, " hash='%s'", enc->cipher.hash);
+    virBufferAddLit(buf, "/>\n");
+
+    if (enc->ivgen.name) {
+        virBufferAsprintf(buf, "<ivgen name='%s'", enc->ivgen.name);
+        if (enc->ivgen.hash)
+            virBufferAsprintf(buf, " hash='%s'", enc->ivgen.hash);
+        virBufferAddLit(buf, "/>\n");
+    }
+}
+
+
 int
 virStorageEncryptionFormat(virBufferPtr buf,
                            virStorageEncryptionPtr enc)
@@ -269,6 +390,11 @@ virStorageEncryptionFormat(virBufferPtr buf,
             return -1;
     }
 
+    virBufferAdjustIndent(buf, 2);
+    if (enc->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS && enc->cipher.name)
+        virStorageEncryptionInfoDefFormat(buf, enc);
+    virBufferAdjustIndent(buf, -2);
+
     virBufferAddLit(buf, "</encryption>\n");
 
     return 0;
diff --git a/src/util/virstorageencryption.h b/src/util/virstorageencryption.h
index b966770..45c5260 100644
--- a/src/util/virstorageencryption.h
+++ b/src/util/virstorageencryption.h
@@ -45,6 +45,16 @@ struct _virStorageEncryptionSecret {
     virSecretLookupTypeDef secdef;
 };
 
+/* For a key type it's possible to dictate the cipher and if necessary iv */
+typedef struct _virStorageEncryptionInfoDef virStorageEncryptionInfoDef;
+typedef virStorageEncryptionInfoDef *virStorageEncryptionInfoDefPtr;
+struct _virStorageEncryptionInfoDef {
+    unsigned int size;
+    char *name;
+    char *mode;
+    char *hash;
+};
+
 typedef enum {
     /* "default" is only valid for volume creation */
     VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT = 0,
@@ -62,6 +72,9 @@ struct _virStorageEncryption {
 
     size_t nsecrets;
     virStorageEncryptionSecretPtr *secrets;
+
+    virStorageEncryptionInfoDef cipher;
+    virStorageEncryptionInfoDef ivgen;
 };
 
 virStorageEncryptionPtr virStorageEncryptionCopy(const virStorageEncryption *src)
diff --git a/tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml b/tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml
new file mode 100644
index 0000000..29ea38a
--- /dev/null
+++ b/tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml
@@ -0,0 +1,41 @@
+<domain type='qemu'>
+  <name>encryptdisk</name>
+  <uuid>496898a6-e6ff-f7c8-5dc2-3cf410945ee9</uuid>
+  <memory unit='KiB'>1048576</memory>
+  <currentMemory unit='KiB'>524288</currentMemory>
+  <vcpu placement='static'>1</vcpu>
+  <os>
+    <type arch='x86_64' machine='pc-i440fx-2.1'>hvm</type>
+    <boot dev='hd'/>
+  </os>
+  <clock offset='utc'/>
+  <on_poweroff>destroy</on_poweroff>
+  <on_reboot>restart</on_reboot>
+  <on_crash>destroy</on_crash>
+  <devices>
+    <emulator>/usr/bin/qemu</emulator>
+    <disk type='file' device='disk'>
+      <driver name='qemu' type='luks'/>
+      <source file='/storage/guest_disks/encryptdisk'/>
+      <target dev='vda' bus='virtio'/>
+      <encryption format='luks'>
+        <secret type='key' uuid='0a81f5b2-8403-7b23-c8d6-21ccc2f80d6f'/>
+      </encryption>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
+    </disk>
+    <disk type='file' device='disk'>
+      <driver name='qemu' type='luks'/>
+      <source file='/storage/guest_disks/encryptdisk2'/>
+      <target dev='vdb' bus='virtio'/>
+      <encryption format='luks'>
+        <secret type='key' usage='mycluster_myname'/>
+      </encryption>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
+    </disk>
+    <controller type='usb' index='0'/>
+    <controller type='pci' index='0' model='pci-root'/>
+    <input type='mouse' bus='ps2'/>
+    <input type='keyboard' bus='ps2'/>
+    <memballoon model='virtio'/>
+  </devices>
+</domain>
diff --git a/tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml b/tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml
new file mode 100644
index 0000000..c04506a
--- /dev/null
+++ b/tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml
@@ -0,0 +1,45 @@
+<domain type='qemu'>
+  <name>encryptdisk</name>
+  <uuid>496898a6-e6ff-f7c8-5dc2-3cf410945ee9</uuid>
+  <memory unit='KiB'>1048576</memory>
+  <currentMemory unit='KiB'>524288</currentMemory>
+  <vcpu placement='static'>1</vcpu>
+  <os>
+    <type arch='x86_64' machine='pc-i440fx-2.1'>hvm</type>
+    <boot dev='hd'/>
+  </os>
+  <clock offset='utc'/>
+  <on_poweroff>destroy</on_poweroff>
+  <on_reboot>restart</on_reboot>
+  <on_crash>destroy</on_crash>
+  <devices>
+    <emulator>/usr/bin/qemu</emulator>
+    <disk type='file' device='disk'>
+      <driver name='qemu' type='luks'/>
+      <source file='/storage/guest_disks/encryptdisk'/>
+      <target dev='vda' bus='virtio'/>
+      <encryption format='luks'>
+        <secret type='key' uuid='0a81f5b2-8403-7b23-c8d6-21ccc2f80d6f'/>
+      </encryption>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
+    </disk>
+    <disk type='file' device='disk'>
+      <driver name='qemu' type='luks'/>
+      <source file='/storage/guest_disks/encryptdisk2'/>
+      <target dev='vdb' bus='virtio'/>
+      <encryption format='luks'>
+        <secret type='key' usage='mycluster_myname'/>
+      </encryption>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
+    </disk>
+    <controller type='usb' index='0'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
+    </controller>
+    <controller type='pci' index='0' model='pci-root'/>
+    <input type='mouse' bus='ps2'/>
+    <input type='keyboard' bus='ps2'/>
+    <memballoon model='virtio'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
+    </memballoon>
+  </devices>
+</domain>
diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c
index fed8351..147eff8 100644
--- a/tests/qemuxml2xmltest.c
+++ b/tests/qemuxml2xmltest.c
@@ -503,6 +503,7 @@ mymain(void)
     DO_TEST("encrypted-disk");
     DO_TEST("encrypted-disk-usage");
     DO_TEST("luks-disks");
+    DO_TEST("luks-disk-cipher");
     DO_TEST("memtune");
     DO_TEST("memtune-unlimited");
     DO_TEST("blkiotune");
diff --git a/tests/storagevolxml2xmlin/vol-luks-cipher.xml b/tests/storagevolxml2xmlin/vol-luks-cipher.xml
new file mode 100644
index 0000000..6c7ad82
--- /dev/null
+++ b/tests/storagevolxml2xmlin/vol-luks-cipher.xml
@@ -0,0 +1,23 @@
+<volume>
+  <name>LuksDemo.img</name>
+  <key>/var/lib/libvirt/images/LuksDemo.img</key>
+  <source>
+  </source>
+  <capacity unit="G">5</capacity>
+  <allocation>294912</allocation>
+  <target>
+    <path>/var/lib/libvirt/images/LuksDemo.img</path>
+    <format type='luks'/>
+    <permissions>
+      <mode>0644</mode>
+      <owner>0</owner>
+      <group>0</group>
+      <label>unconfined_u:object_r:virt_image_t:s0</label>
+    </permissions>
+    <encryption format='luks'>
+      <secret type='key' usage='mumblyfratz'/>
+      <cipher name='serpent' size='256' mode='cbc' hash='sha256'/>
+      <ivgen name='plain64' hash='sha256'/>
+    </encryption>
+  </target>
+</volume>
diff --git a/tests/storagevolxml2xmlout/vol-luks-cipher.xml b/tests/storagevolxml2xmlout/vol-luks-cipher.xml
new file mode 100644
index 0000000..22a7383
--- /dev/null
+++ b/tests/storagevolxml2xmlout/vol-luks-cipher.xml
@@ -0,0 +1,23 @@
+<volume type='file'>
+  <name>LuksDemo.img</name>
+  <key>/var/lib/libvirt/images/LuksDemo.img</key>
+  <source>
+  </source>
+  <capacity unit='bytes'>5368709120</capacity>
+  <allocation unit='bytes'>294912</allocation>
+  <target>
+    <path>/var/lib/libvirt/images/LuksDemo.img</path>
+    <format type='luks'/>
+    <permissions>
+      <mode>0644</mode>
+      <owner>0</owner>
+      <group>0</group>
+      <label>unconfined_u:object_r:virt_image_t:s0</label>
+    </permissions>
+    <encryption format='luks'>
+      <secret type='key' usage='mumblyfratz'/>
+      <cipher name='serpent' size='256' mode='cbc' hash='sha256'/>
+      <ivgen name='plain64' hash='sha256'/>
+    </encryption>
+  </target>
+</volume>
diff --git a/tests/storagevolxml2xmltest.c b/tests/storagevolxml2xmltest.c
index a36a706..db82bea 100644
--- a/tests/storagevolxml2xmltest.c
+++ b/tests/storagevolxml2xmltest.c
@@ -106,6 +106,7 @@ mymain(void)
     DO_TEST("pool-dir", "vol-qcow2-0.10-lazy");
     DO_TEST("pool-dir", "vol-qcow2-nobacking");
     DO_TEST("pool-dir", "vol-luks");
+    DO_TEST("pool-dir", "vol-luks-cipher");
     DO_TEST("pool-disk", "vol-partition");
     DO_TEST("pool-logical", "vol-logical");
     DO_TEST("pool-logical", "vol-logical-backing");
-- 
2.5.5

--
libvir-list mailing list
libvir-list@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/libvir-list



[Index of Archives]     [Virt Tools]     [Libvirt Users]     [Lib OS Info]     [Fedora Users]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]     [Fedora Tools]