December 3, 2019

Getting Started with Minikube on Fedora 30

Introduction

To install Minikube there are 3 things to do:

  1. Install a hypervisor, e.g. VirtualBox, KVM
  2. "Install" (download and add in path) kubectl
  3. "Install" (download and add in path) Minikube

Install kubectl

Reference: https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-on-linux


$ curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl

$ chmod +x ./kubectl

$ sudo mv ./kubectl /usr/local/bin/kubectl

$ kubectl version

Install MiniKube


$ wget https://github.com/kubernetes/minikube/releases/download/v1.6.0-beta.1/minikube-1.6.0.beta.1.rpm .
    
$ sudo rpm -pi minikube-1.6.0.beta.1.rpm

$ sudo rpm -ql minikube
/usr/bin/minikube

$ sudo rpm -q --scripts minikube

Test

https://kubernetes.io/docs/setup/learning-environment/minikube/

Start Minikube and create a cluster. Here I specify VirtualBox as hypervisor, since I have also KVM installed.


$ minikube start --vm-driver=virtualbox

Deploy image 'k8s.gcr.io/echoserver' version 1.10 and name deployment 'hello-minikube'.


$ kubectl create deployment hello-minikube --image=k8s.gcr.io/echoserver:1.10

Expose deployment on NodePort and port 8080, i.e. create routing.


$ kubectl expose deployment hello-minikube --type=NodePort --port=8080

Check deployment has finished.


$ kubectl get pod

Retrieve internal IP and port and access web app.


$ minikube service hello-minikube --url
http://192.168.99.100:30024

$ curl http://192.168.99.100:30024
Hostname: hello-minikube-797f975945-fmswk

Pod Information:
 -no pod information available-

Server values:
 server_version=nginx: 1.13.3 - lua: 10008

Request Information:
 client_address=172.17.0.1
 method=GET
 real path=/
 query=
 request_version=1.1
 request_scheme=http
 request_uri=http://192.168.99.100:8080/

Request Headers:
 accept=*/*
 host=192.168.99.100:30024
 user-agent=curl/7.65.3

Request Body:
 -no body in request-

December 2, 2019

How to Install VirtualBox 6 on Fedora 30

https://www.if-not-true-then-false.com/2010/install-virtualbox-with-yum-on-fedora-centos-red-hat-rhel/

Introduction to YAML

Introduction

The YAML format has grown in popularity and is used for example in Kubernetes and Ansible.

Comparison XML, JSON and YAML

XML JSON YAML

<Servers>
    <Server>
        <id>12345</id>
        <name>My Web Server</name>
        <status>ACTIVE</status>
    </Server>
</Servers>

{
    "Servers": [
        {
            "id": "12345",
            "name": "My Web Server",
            "status": "ACTIVE"
        }
    ]
}

Servers:
    -   id: 1234
        name: My Web Server
        status: ACTIVE

Tools for validating JSON

There are numerous tools out that for JSON, one for Linux and Bash is jq - a lightweight and flexible command-line JSON processor.


$ sudo yum install jq

$ cat servers.json | jq '.Servers[0].id'
"12345"

Tools for validating YAML

And a Linux and Bash tool for YAML is yq


$ sudo yum install python3 python3-pip
$ sudo pip3 install yq

$ cat servers.yaml | yq '.'
{
  "Servers": [
    {
      "id": 1234,
      "name": "My Web Server",
      "status": "ACTIVE"
    }
  ]
}

YAML Data Types

WARNING: YAML is not structured in the same sense as XML, JSON, XHTML, etc. It uses spaces (and not tabs and do not mix space and tabs) as seperator. So pay extra attention to how many spaces you use.

Key Value Pair


Fruit: Orange
Vegetable: Lettuce
Liquid: Wine

$ cat key_value_pair.yaml | yq '.'
{
  "Fruit": "Orange",
  "Vegetable": "Lettuce",
  "Liquid": "Wine"
}

List (Array)


Fruits:
-   Orange
-   Apple
-   Banana

Vegetables:
-   Carrot
-   Tomatoes
-   Onion

$ cat array_list.yaml | yq '.'
{
  "Fruits": [
    "Orange",
    "Apple",
    "Banana"
  ],
  "Vegetables": [
    "Carrot",
    "Tomatoes",
    "Onion"
  ]
}

Dictionary (Map)


Banana:
    Calories: 105
    Fat: 0.4 g
    Carbs: 31 g

Grapes:
    Calories: 27
    Fat: 0.7 g
    Carbs: 56 g    

$ cat dictionary_map.yaml | yq '.'
{
  "Banana": {
    "Calories": 105,
    "Fat": "0.4 g",
    "Carbs": "31 g"
  },
  "Grapes": {
    "Calories": 27,
    "Fat": "0.7 g",
    "Carbs": "56 g"
  }
}

More Advanced Examples. List of Dictionary (Array of Map)


Fruits:
-   Orange:
        Calories: 105
        Fat: 0.4 g
        Carbs: 31 g
-   Apple:
        Calories: 27
        Fat: 0.7 g
        Carbs: 56 g
        
$ cat array_list_of_dictionary_map.yaml | yq '.'

$ cat array_list_of_dictionary_map.yaml | yq '.Fruits[1]'
{
  "Apple": {
    "Calories": 27,
    "Fat": "0.7 g",
    "Carbs": "56 g"
  }
}

Differences between List and Dictionary

Dictionary - Unordered

List - Ordered

November 29, 2019

How to Install IDM Master and Replica on RHEL 7

Minimum Hardware

4 GB RAM

https://bugzilla.redhat.com/show_bug.cgi?id=1436295

Prerequisite

I have created two virtual machine, since this is a development setup I will hardcode hostname and IP in /etc/hosts and manually set hostnames.

  • rhel7.7-idm-master.magnuskkarlsson.local
    • 192.168.122.113
  • rhel7.7-idm-replica1.magnuskkarlsson.local
    • 192.168.122.99

# cat /etc/redhat-release 
Red Hat Enterprise Linux Server release 7.7 (Maipo)

# systemctl stop firewalld; systemctl disable firewalld

# hostnamectl set-hostname rhel7.7-idm-master.magnuskkarlsson.local
# hostnamectl set-hostname rhel7.7-idm-replica1.magnuskkarlsson.local

# echo "192.168.122.113 rhel7.7-idm-master.magnuskkarlsson.local" >> /etc/hosts
# echo "192.168.122.99 rhel7.7-idm-replica1.magnuskkarlsson.local" >> /etc/hosts

Update Date & Time

Make sure NTP is setup and synchronized.


systemctl restart chronyd
chronyc sources
chronyc tracking
timedatectl

Installation of IDM Master (version 4.6.5)

Install IDM Master on rhel7.7-idm-master.magnuskkarlsson.local.


# yum install -y ipa-server

# ipa-server-install --domain magnuskkarlsson.local \
    --realm MAGNUSKKARLSSON.LOCAL \
    -p foo123123 -a foo123123 -U 

Installation of IDM Replica (version 4.6.5)

Install IDM Replica on rhel7.7-idm-replica1.magnuskkarlsson.local.

First install idm/ipa client and register host in idm. Then setup host as replica.


# yum install -y ipa-server

# ipa-client-install --server=rhel7.7-idm-master.magnuskkarlsson.local \
    --domain=magnuskkarlsson.local \
    --principal=admin \
    --password=foo123123 -U

# ipa-replica-install --setup-ca --principal=admin --admin-password=foo123123 -U

Test

First kerberos login on master - rhel7.7-idm-master.magnuskkarlsson.local and add a user.


# kinit admin

# ipa user-add --first="Magnus K" \
    --last=Karlsson \
    --cn="Magnus K Karlsson" \
    --principal=magnuskkarlsson \
    --password \
    --all magnuskkarlsson

# ipa user-find magnuskkarlsson

Then kerberos login on replica - rhel7.7-idm-replica1.magnuskkarlsson.local and search for user and check that user is replicated.


# kinit admin

# ipa user-find magnuskkarlsson

November 8, 2019

Eclipse Microprofile LDAP Health Check with Java EE 8 and JBoss EAP 7.2

Introduction

In my previous blog I wrote about Eclipse Microprofile Health with Java EE 8 and JBoss EAP 7.2 and pointed to several Health Checks built in Spring, but for LDAP Springs Health Check is not very good. A better implementation is suggested below. And also based on standard Eclipse Microprofile Health.

Eclipse Microprofile LDAP Health Check


package se.magnuskkarlsson.example.microprofile;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;

import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.health.Health;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;

@Health
@ApplicationScoped
public class LDAPHealthCheck implements HealthCheck {

    private final Logger log = Logger.getLogger(LDAPHealthCheck.class.getName());

    @Inject
    @ConfigProperty(name = "ldapHealthCheck.providerURL")
    protected String providerURL;

    @Inject
    @ConfigProperty(name = "ldapHealthCheck.securityPrincipal")
    protected String securityPrincipal;

    @Inject
    @ConfigProperty(name = "ldapHealthCheck.securityCredentials")
    protected String securityCredentials;

    @Inject
    @ConfigProperty(name = "ldapHealthCheck.baseCtxDN")
    protected String baseCtxDN;

    @PostConstruct
    public void init() {
    }

    @Override
    public HealthCheckResponse call() {
        List<String> result = null;
        try {
            result = searchBaseContextDN();
        } catch (Exception e) {
            log.log(Level.SEVERE, "Failed to perform LDAP health check search baseCtxDN='" + baseCtxDN + "'.", e);
        }
        boolean state = (result != null && !result.isEmpty()) ? true : false;
        String data = (result != null && !result.isEmpty()) ? result.toString() : null;
        return HealthCheckResponse.named("ldap-health-check").withData(baseCtxDN, data).state(state).build();
    }

    protected List<String> searchBaseContextDN() throws NamingException {
        InitialLdapContext ctx = null;
        try {
            Properties env = new Properties();
            env.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
            env.setProperty(Context.PROVIDER_URL, providerURL);
            env.setProperty(Context.SECURITY_AUTHENTICATION, "simple");
            env.setProperty(Context.SECURITY_PRINCIPAL, securityPrincipal);
            env.put(Context.SECURITY_CREDENTIALS, securityCredentials);

            log.info("Logging into LDAP server, env=" + env);
            ctx = new InitialLdapContext(env, null);
            log.info("Logged into LDAP server, " + ctx);

            // filter
            String filter = "(objectClass=*)";

            // scope
            SearchControls ctls = new SearchControls();
            ctls.setSearchScope(SearchControls.ONELEVEL_SCOPE);

            // search for objects using filter and scope
            NamingEnumeration<SearchResult> answer = ctx.search(baseCtxDN, filter, ctls);
            List<String> result = new ArrayList<String>();
            while (answer.hasMore()) {
                SearchResult searchResult = answer.next();
                if (searchResult != null) {
                    result.add(searchResult.toString());
                }
            }

            log.info("Result base context dn, " + result);
            return result;
        } finally {
            if (ctx != null) {
                try {
                    // Close the context when we're done
                    ctx.close();
                } catch (NamingException IGNORE) {
                }
            }
        }
    }

}

Test

http://127.0.0.1:9990/health


[standalone@localhost:9990 /] /subsystem=microprofile-health-smallrye:check

October 29, 2019

How To Install VisualVM on Fedora 30 and OpenJDK 11

Installation

VisualVM is not bundle with OpenJDK 11, but can be easily downloaded, unzipped and ran.

https://visualvm.github.io/download.html


$ wget https://github.com/visualvm/visualvm.src/releases/download/1.4.4/visualvm_144.zip

$ unzip visualvm_144.zip

$ visualvm_144/bin/visualvm

Feature

https://visualvm.github.io/features.html

October 22, 2019

How to Read MSUPN in X509Certificate

Maven dependency


        <!-- The prov module provides all the JCA/JCE provider functionality. -->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>${bouncycastle.version}</version>
        </dependency>
        <!-- The pkix module is the home for code for X.509 certificate generation 
            and the APIs for standards that rely on ASN.1 such as CMS, TSP, PKCS#12, OCSP, CRMF, 
            and CMP. -->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk15on</artifactId>
            <version>${bouncycastle.version}</version>
        </dependency>

The source code for The Bouncy Castle Crypto Package For Java.

The Java code


package se.magnuskkarlsson.example.bouncycastle;

import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1String;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.asn1.x509.GeneralName;

public class X509CertificateUtils {

    public static final String MSUPN_OID = "1.3.6.1.4.1.311.20.2.3";
    private final Logger log = Logger.getLogger(X509CertificateUtils.class.getName());

    public String getMSUPNFromX509Certificate(X509Certificate cert) throws CertificateParsingException {
        Collection<List<?>> sans = JcaX509ExtensionUtils.getSubjectAlternativeNames(cert);
        for (List<?> san : sans) {

            log.info("Read X509 SAN " + sans);
            int sanType = (int) san.get(0);
            if (sanType == GeneralName.otherName) {

                ASN1Sequence sanASN1Sequence = (ASN1Sequence) san.get(1);
                String msupn = getSANFromASN1Sequence(sanASN1Sequence);
                if (msupn != null) {
                    return msupn;
                }
            }
        }
        return null;
    }

    private String getSANFromASN1Sequence(ASN1Sequence sanASN1Sequence) {
        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) sanASN1Sequence.getObjectAt(0);
        if (!MSUPN_OID.equals(oid.getId())) {
            log.warning("Invalid MSUPN OID, expected '" + MSUPN_OID + "' got '" + oid.getId() + "'.");
            return null;
        }

        ASN1TaggedObject sanASN1TaggedObject = (ASN1TaggedObject) sanASN1Sequence.getObjectAt(1);
        ASN1Primitive sanASN1Primitive = sanASN1TaggedObject.getObject();

        if (sanASN1Primitive instanceof ASN1String) {
            return ((ASN1String) sanASN1Primitive).getString();
        }
        log.warning("Invalid ASN.1 Primitive class, expected ASN1String, got " + sanASN1Primitive.getClass());
        return null;
    }

}