Construye tu propia puerta de acceso

Implementa tu propia pasarela de acceso sobre OpenResty (Nginx), sin necesidad de reescribirlas.

5
 min read

En el post anterior, analizamos cómo la contenedorización facilita la transición de los sistemas de identidad heredados, lo que nos permite crear entornos de forma fácil y rápida que puedan ejecutarse sin problemas en Amazon EC2, Google Cloud, Azure, Virtualbox y otros. Mostramos cómo funciona esto específicamente, con CA Siteminder. Más allá de eso, en este artículo exploraremos una solución sin necesidad de código para incorporar aplicaciones que no sean de Siteminder.

El SSO en la nube basado en estándares puede ayudar, pero...

Cuando «levantamos y cambiamos» un sistema de identidad heredado, el entorno de nube alojará nuevas aplicaciones y servicios que no formaban parte de la configuración anterior. Habilitar el inicio de sesión único (SSO) para las aplicaciones web más modernas que cuentan con SAML u OpenID Connect (OIDC) debería ser bastante sencillo, ya que Siteminder funciona perfectamente con ambos estándares.

En este escenario, Siteminder desempeñará el papel de proveedor de identidad (IdP) y la aplicación asumirá el papel de proveedor de servicios (SP). De esta forma, podemos evitar el esfuerzo de reescribir la aplicación y, al mismo tiempo, extender el inicio de sesión único a cualquier aplicación nueva. Esto significa que el usuario no tiene que volver a introducir sus credenciales al intentar acceder a ninguna de las nuevas aplicaciones o servicios. Hemos obtenido dos ventajas empresariales: un menor esfuerzo y una mejor experiencia de usuario.

Cuando esto se queda corto

De forma predeterminada, Siteminder protege las aplicaciones mediante una pasarela de acceso, también conocida como servidor proxy seguro (SPS). En pocas palabras, el Access Gateway intercepta las solicitudes, autentica y autoriza a los usuarios antes de que se les conceda el acceso a la aplicación de destino. La aplicación no conoce Siteminder, ya que se basa en los encabezados HTTP estándar empleados por Access Gateway para identificar al usuario, así como en varias otras afirmaciones.

Idealmente, esto debería servir como una solución alternativa, con el objetivo de minimizar las interrupciones y, al mismo tiempo, abordar las tareas de migración a más largo plazo. La desventaja es que la deuda técnica (en lo que respecta a la modernización de la IAM) aumentará, ya que el legado del que se está intentando dejar atrás ahora está haciendo más que antes. Además, como parte del nuevo ecosistema, es muy probable que haya aplicaciones que no admitan el SSO basado en estándares (es decir, no SAML ni OIDC) o que no estén basadas en la web (por ejemplo, las aplicaciones de Windows que realizan la autenticación de Active Directory). Por último, el entorno heredado basado en la nube aumentará los costos operativos debido al mantenimiento.

Los riesgos en los que se incurre pueden incluir:

* Aumento de la deuda técnica
* Pérdida de soporte para el SSO basado en estándares
* Aumento de los gastos operativos

¿Cómo podemos reducir al mínimo las interrupciones y, al mismo tiempo, reducir la deuda técnica de nuestra implementación de IAM?

Asumir la titularidad del «pegamento identitario»

Vale la pena señalar que Access Gateway es esencialmente un proxy inverso creado en torno a un servidor web Apache, configurado con un conjunto de módulos específicos de Siteminder. En realidad, el núcleo interno se encuentra en esos complementos, que se encargan de la autenticación y la autorización en nombre de las aplicaciones posteriores.

En los últimos años, el servidor web Nginx ha ido ganando popularidad de manera constante debido a su naturaleza liviana y su compatibilidad con la arquitectura de microservicios. Por lo tanto, es probable que Nginx forme parte de su arquitectura en la nube. Dado que es funcionalmente equivalente y brilla en términos de extensibilidad, ¿por qué no implementar además nuestra propia pasarela de acceso?

La buena noticia es que puedes extender Nginx hasta el núcleo sin la carga adicional de escribir un complemento. Simplemente puede implementar lo que desee, utilizando scripts de Lua simples y sencillos. El nombre de esta combinación de Nginx y Lua (más precisamente Luajit) se llama OpenResty. Es de código abierto y tiene una comunidad enorme.

Arquitectura de referencia de Siteminder con puerta de enlace de acceso personalizada


Pero, ¿cómo se espera que Nginx se comunique con Siteminder (más específicamente, el Policy Server)?
FFI es tu amigo aquí. Según Wikipedia:

UN interfaz de función externa (FFI) es un mecanismo mediante el cual un programa escrito en una lenguaje de programación puede llamar a rutinas o hacer uso de servicios escritos en otra.

En términos sencillos, lo que esto significa para nuestro escenario es lo siguiente: podemos invocar el SDK del agente de Siteminder desde LuaJit, lo que nos permite hacernos pasar por un agente de Siteminder en sentido ascendente, lo que protege las aplicaciones en sentido descendente, tal como lo hacía la pasarela de acceso. Para hacer cumplir las reglas de proxy de las pasarelas de acceso, solo tenemos que seguir la ruta programática: escribir las condiciones de Lua if-then else correspondientes.

Adiós, Access Gateway, archivos XML, ProxyUI y amigos: ahora solo tenemos el proxy inverso basado en OpenREST y el Policy Server. Las aplicaciones ni siquiera notarán el cambio, ya que se cumple el contrato habitual, es decir, mediante encabezados HTTP.

Por último, pero no por ello menos importante, una vez que Lua interactúe con Siteminder, podremos volver a utilizar la misma implementación para proteger no solo las aplicaciones web, sino también las que no tengan acceso directo. No es necesario volver a implementar todo en Perl.

Caso práctico: generar un token SSO válido de Siteminder

Configure su entorno de desarrollo

Para crear un entorno portátil, recomendamos utilizar un enfoque basado en contenedores. En este caso, usaremos Docker.
Este es un ejemplo de dockerfile que define una imagen que contiene un entorno de desarrollo de Siteminder:


FROM openresty/openresty:centos

ENV PS_ZIP=ps-12.8-sp05-linux-x86-64.zip \ 
    SDK_ZIP=smsdk-12.8-sp05-linux-x86-64.zip \
    BASE_DIR=/opt/CA/siteminder \
    INSTALL_TEMP=/tmp/sm_temp

ENV SCRIPT_DIR=${INSTALL_TEMP}/dockertools 

#
# Creation of User, Directories and Installation of OS packages
# ----------------------------------------------------------------
RUN dnf config-manager --set-enabled powertools
RUN yum install -y which unzip rng-tools java-1.8.0-openjdk-1:1.8.0.292.b10-1.el8_4.x86_64 \
    ksh openldap-clients openssh-server xauth libnsl gcc gcc-c++ openmotif
RUN groupadd smuser && \
    useradd smuser -g smuser
RUN mkdir -p ${BASE_DIR} && \
    chmod a+xr ${BASE_DIR} && \ 
    chown smuser:smuser ${BASE_DIR} 
RUN mkdir -p ${INSTALL_TEMP} && \
    chmod a+xr ${INSTALL_TEMP} && chown smuser:smuser ${INSTALL_TEMP} 

# Increase entropy
# ----------------
RUN mv /dev/random /dev/random.org && \
    ln -s /dev/urandom /dev/random

# Copy packages and scripts
# -------------------------
COPY --chown=smuser:smuser install/* ${INSTALL_TEMP}/
COPY --chown=smuser:smuser ca-ps-installer.properties ${INSTALL_TEMP}/
COPY --chown=smuser:smuser sdk-installer.properties ${INSTALL_TEMP}/

# Install Policy Server
# -------------------------
RUN unzip ${INSTALL_TEMP}/${PS_ZIP} -d ${INSTALL_TEMP} && \
    chmod +x ${INSTALL_TEMP}/ca-ps-12.8-sp05-linux-x86-64.bin && \
    ${INSTALL_TEMP}/ca-ps-12.8-sp05-linux-x86-64.bin -i silent -f ${INSTALL_TEMP}/ca-ps-installer.properties

RUN echo ". /opt/CA/siteminder/ca_ps_env.ksh" >> /home/smuser/.bash_profile

# Install the SDK
# -----------------------------------------------
RUN unzip ${INSTALL_TEMP}/${SDK_ZIP} -d ${INSTALL_TEMP} && \
    chmod +x ${INSTALL_TEMP}/ca-sdk-12.8-sp05-linux-x86-64.bin && \
    ${INSTALL_TEMP}/ca-sdk-12.8-sp05-linux-x86-64.bin -i silent -f ${INSTALL_TEMP}/sdk-installer.properties

USER smuser

# Define default command to start bash.
ENTRYPOINT ["/bin/bash"]

La imagen generada se basará en la imagen de OpenResty de CentOS en lugar de en la imagen de stock, por lo que solo necesitamos incluir los paquetes específicos de Siteminder en la imagen. Nota: Estamos instalando el paquete Policy Server, pero no vamos a continuar con la fase de configuración. Esto se debe a que solo necesitamos las bibliotecas dinámicas que proporciona, así como las herramientas, para conectarnos con un Policy Server externo y en funcionamiento.

Cree la imagen con el siguiente comando:


$ docker build -t mysmdev .

Luego ejecútelo usando:


$ docker run -t -i mysmdev

Para poder conectarnos al Policy Server, necesitamos generar un descriptor SMHost.conf y hacer referencia a él desde el script Lua.

Agrupe los stubs FFI de LuaJit para la API del agente

Definamos primero nuestros tipos de FFI para la API del agente:


local _M = {
  --
  -- Function return codes
  --
  SM_AGENTAPI_NOCONNECTION                = -3,
  SM_AGENTAPI_TIMEOUT                     = -2,
  SM_AGENTAPI_FAILURE                     = -1,
  SM_AGENTAPI_SUCCESS                     = 0,
  SM_AGENTAPI_YES                         = 1,
  SM_AGENTAPI_NO                          = 2,
  SM_AGENTAPI_CHALLENGE                   = 3,
  SM_AGENTAPI_UNRESOLVED                  = 4,
  SM_GETBOOTSTRAPCONFIG_FAILURE           = 5,
  SM_AGENTAPIINIT_FAILURE                 = 6,
  SM_FETCHCONFIGDATA_FAILURE              = 7,
  SM_AGENTAPI_ERR_HCO_NOT_ENABLED         = 8,
  SM_AGENTAPI_ERR_HCO_NOT_OURS            = 9,
  SM_AGENTAPI_ERR_HCO_NOT_CHANGED         = 10,
  SM_AGENTAPI_YES_DLP                     = 11,
  SM_AGENTAPI_INVALID_ARGS                = 12,
  --
  -- Attributes
  --
  SM_AGENTAPI_ATTR_AUTH_DIR_OID           = 151,
  SM_AGENTAPI_ATTR_AUTH_DIR_NAME          = 213,
  SM_AGENTAPI_ATTR_AUTH_DIR_SERVER        = 214,
  SM_AGENTAPI_ATTR_AUTH_DIR_NAMESPACE     = 215,
  SM_AGENTAPI_ATTR_USERMSG                = 216,
  SM_AGENTAPI_ATTR_USERDN                 = 218,
  SM_AGENTAPI_ATTR_USERUNIVERSALID        = 152,
  SM_AGENTAPI_ATTR_IDENTITYSPEC           = 156,
  -- Well-known attributes used by the Single Sign-On APIs
  -- not previously defined above                 
  SM_AGENTAPI_ATTR_STARTSESSIONTIME       = 154,
  SM_AGENTAPI_ATTR_LASTSESSIONTIME        = 155,
  SM_AGENTAPI_ATTR_DEVICENAME             = 200,
  SM_AGENTAPI_ATTR_SESSIONID              = 205,
  SM_AGENTAPI_ATTR_CLIENTIP               = 208,
  SM_AGENTAPI_ATTR_SESSIONSPEC            = 209,
  SM_AGENTAPI_ATTR_USERNAME               = 210,
  SM_AGENTAPI_ATTR_IDLESESSIONTIMEOUT     = 225,
  SM_AGENTAPI_ATTR_MAXSESSIONTIMEOUT      = 226,
  SM_AGENTAPI_ATTR_SSOZONE                = 228,
  SM_AGENTAPI_ATTR_SESSION_DOMAIN         = 229,
  -- Well-known attributes used by the Authentication Chain 
  -- not previously defined above                 
  SM_AGENTAPI_ATTR_AUTHCHAINSPEC          = 240,
  SM_AGENTAPI_ATTR_CHAINREALMCREDENTIALS  = 219,
  SM_AGENTAPI_ATTR_CHAINFORMLOC           = 220,
}

local mt = {__index = _M}

ffi.cdef [[
    typedef struct Sm_AgentApi_Server_s
    {
        char    lpszIpAddr[256];       
        long    nConnMin;                                
        long    nConnMax;                                
        long    nConnStep;                               
        long    nTimeout;                                 
        long    nPort[3];                                 
        void*   pHandle[3];                               
        long    nClusterSeq;                              
    } Sm_AgentApi_Server_t;

    typedef struct Sm_AgentApi_Init_s
    {
      long    nVersion;                                 
      char    lpszHostName[256];      
      char    lpszSharedSecret[256];  
      long    nFailover;                               
      long    nNumServers;                              
      Sm_AgentApi_Server_t*    pServers;               
    } Sm_AgentApi_Init_t;

    typedef struct Sm_AgentApi_Attribute_s
    {
      long    nAttributeId;
      long    nAttributeTTL;
      long    nAttributeFlags;
      char    lpszAttributeOid[64];
      long    nAttributeLen;
      char*   lpszAttributeValue;
    } Sm_AgentApi_Attribute_t;

    typedef struct Sm_AgentApi_Session_s
    {
      int32_t nReason;
      int32_t nIdleTimeout;
      int32_t nMaxTimeout;
      int32_t nCurrentServerTime;
      int32_t nSessionStartTime;
      int32_t nSessionLastTime;
      char  lpszSessionId[64];
      char  lpszSessionSpec[2048];
    } Sm_AgentApi_Session_t;

    typedef struct Sm_AgentApi_ResourceContext_s
    {
      char    lpszAgent[256];
      char    lpszServer[256];
      char    lpszAction[256];
      char    lpszResource[8192];
    } Sm_AgentApi_ResourceContext_t;

    typedef struct Sm_AgentApi_Realm_s
    {
      char    lpszDomainOid[64];
      char    lpszRealmOid[64];
      char    lpszRealmName[256];
      long    nRealmCredentials;
      char    lpszFormLocation[8192];
    } Sm_AgentApi_Realm_t;

    typedef struct Sm_AgentApi_UserCredentials_s
    {
      int32_t nChallengeReason;
      char    lpszUsername[256];
      char    lpszPassword[4096];
      char    lpszCertUserDN[1024];
      char    lpszCertIssuerDN[1024];
      int32_t nCertBinaryLen;
      char*    lpszCertBinary;
    } Sm_AgentApi_UserCredentials_t;

    int Sm_AgentApi_GetConfig (Sm_AgentApi_Init_t* pInit, const char *lpszAgentName, const char *lpszPath);
    int Sm_AgentApi_Init (const Sm_AgentApi_Init_t* pInitStruct, void** ppHandle);
    int Sm_AgentApi_SetDefaultAgentId(const char *pszAgentIdentity, void* pHandle);
    int Sm_AgentApi_IsProtected (
      const void* pHandle,
      const char* lpszClientIpAddr,
      const Sm_AgentApi_ResourceContext_t* pResourceContext,
      Sm_AgentApi_Realm_t* pRealm);
    int Sm_AgentApi_Authorize (
      const void* pHandle,
      const char* lpszClientIpAddr,                                /* optional */
      const char* lpszTransactionId,                               /* optional */
      const Sm_AgentApi_ResourceContext_t* pResourceContext,
      const Sm_AgentApi_Realm_t* pRealm,
      Sm_AgentApi_Session_t* pSession,
      long* pNumAttributes,
      Sm_AgentApi_Attribute_t** ppAttributes);

    int Sm_AgentApi_CreateSSOToken (const void* pHandle, Sm_AgentApi_Session_t* pSession, long nNumAttributes,
      Sm_AgentApi_Attribute_t* pTokenAttributes, long* pNumSSOTokenLength , char* lpszSSOToken); 
    int Sm_AgentApi_Login (
        const void* pHandle,
        const char* lpszClientIpAddr,                                /* optional */
        const Sm_AgentApi_ResourceContext_t* pResourceContext,
        const Sm_AgentApi_Realm_t* pRealm,
        const Sm_AgentApi_UserCredentials_t* pUserCredentials,
        Sm_AgentApi_Session_t* pSession,
        long* pNumAttributes,
        Sm_AgentApi_Attribute_t** ppAttributes);
    void Sm_AgentApi_FreeAttributes (const long nNumAttributes, const Sm_AgentApi_Attribute_t* pAttributes);
    int Sm_AgentApi_DecodeSSOToken(
        const void* pHandle,
        const char* lpszSSOToken,
        long* nTokenVersion,
        long* pThirdPartyToken,
        long* pNumAttributes,
        Sm_AgentApi_Attribute_t** ppTokenAttributes,
        long nUpdateToken,
        long* pNumUpdatedSSOTokenLength,
        char* lpszUpdatedSSOToken);
]]

Aunque se parecen a las declaraciones que normalmente se encuentran en los archivos de encabezado en «C», en este contexto se utilizan para los scripts de Lua, para poder definir la forma de los datos que se intercambiarán con la biblioteca dinámica del agente.

A continuación, declaramos las funciones del agente que funcionan en estos tipos:


function _M.getconfig(self, smhostpath)
    local agentapi = ffi.new("Sm_AgentApi_Init_t")
    smagentapilib.Sm_AgentApi_GetConfig(agentapi, nil, smhostpath)
    return agentapi
end

function _M.init(agentapi, phandle)
    return smagentapilib.Sm_AgentApi_Init(agentapi, phandle)
end

function _M.boot(smhostpath, agentname)
    local pSmApiHandle = ffi.new("void*[1]")
    local agentapi = _M.getconfig(smhostpath)
    _M.init(agentapi, pSmApiHandle)
    if smagentapilib.Sm_AgentApi_SetDefaultAgentId(agentname, pSmApiHandle[0]) ==
        _M.SM_AGENTAPI_FAILURE then return -1 end
    return pSmApiHandle[0]
end

function _M.is_protected(file, method, pSmApiHandle)
    local resourceContext = ffi.new("Sm_AgentApi_ResourceContext_t")
    local realm = ffi.new("Sm_AgentApi_Realm_t")

    ffi.copy(resourceContext.lpszResource, file)
    ffi.copy(resourceContext.lpszAction, method)
    ffi.copy(resourceContext.lpszServer, "extapp")

    local res = smagentapilib.Sm_AgentApi_IsProtected(pSmApiHandle, "127.0.0.1",
                                                      resourceContext, realm)
    return res, resourceContext, realm
end

function _M.login(loginName, password, resourceContext, realm, pSmApiHandle)
    local userCreds = ffi.new("Sm_AgentApi_UserCredentials_t")
    local session = ffi.new("Sm_AgentApi_Session_t")
    local iNumAttributes = ffi.new("long[1]", 0)
    local pAttributes = ffi.new("Sm_AgentApi_Attribute_t*[1]")

    ffi.copy(userCreds.lpszUsername, loginName)
    ffi.copy(userCreds.lpszPassword, password)

    local res = smagentapilib.Sm_AgentApi_Login(pSmApiHandle, "127.0.0.1",
                                                resourceContext, realm,
                                                userCreds, session,
                                                iNumAttributes, pAttributes)
    return res, session, iNumAttributes, pAttributes
end

function _M.sso(pSmApiHandle, pszUserDN, session, numAttributes)
    if pSmApiHandle == nil then return -1 end

    local pAttr = ffi.new("Sm_AgentApi_Attribute_t[3]")
    local pszIPAddress = "123.45.67.89"
    local pszSSOToken = ffi.new("char[2048]")
    local nTlen = ffi.new("long[1]", 2048)
    local result = 0
    local attrCount = ffi.new("long", 1)
    ffi.fill(pszSSOToken, ffi.sizeof("char[2048]"))

    pAttr[0].nAttributeId = _M.SM_AGENTAPI_ATTR_USERDN
    pAttr[0].lpszAttributeValue = ffi.new("char[?]", string.len(pszUserDN) + 1) -- new char[strlen(pszUserDN)+1];
    ffi.copy(pAttr[0].lpszAttributeValue, pszUserDN)

    local res = smagentapilib.Sm_AgentApi_CreateSSOToken(pSmApiHandle, session,
                                                         numAttributes, pAttr,
                                                         nTlen, pszSSOToken)
    return res, ffi.string(pszSSOToken)
end

function _M.decode_sso_token(pSmApiHandle, ssoToken)
    local nTokenVer = ffi.new("long[1]", 0)
    local nNumDecAttr = ffi.new("long[1]", 0)
    local nNumThdParty = ffi.new("long[1]", 0)
    local nNumUpdateToken = 1
    local nNumUpdateTokenLength = ffi.new("long[1]", 2048)
    local pszUpdatedSSOToken = ffi.new("char[2048]")
    local pTokenAttributes = ffi.new("Sm_AgentApi_Attribute_t*[1]")

    if pSmApiHandle == nil then return -1 end

    local result = smagentapilib.Sm_AgentApi_DecodeSSOToken(pSmApiHandle,
                                                            ssoToken, nTokenVer,
                                                            nNumThdParty,
                                                            nNumDecAttr,
                                                            pTokenAttributes,
                                                            nNumUpdateToken,
                                                            nNumUpdateTokenLength,
                                                            pszUpdatedSSOToken)

    local tokenAttrs = {}                                                        
    for i = 1, tonumber(nNumDecAttr[0]) do
        local pTemp = pTokenAttributes[0][i - 1]
        tokenAttrs[tonumber(pTemp.nAttributeId)] = tostring(ffi.string(pTemp.lpszAttributeValue))
    end

    smagentapilib.Sm_AgentApi_FreeAttributes(nNumDecAttr[0], pTokenAttributes[0]);

    return result, nNumDecAttr, tokenAttrs, ffi.string(pszUpdatedSSOToken)
end

return _M

Al igual que los tipos declarados anteriormente, también asignan una a una las funciones de la biblioteca del agente.

Invocar al agente desde OpenResty

Una característica interesante de OpenResty es que el código se puede ejecutar sin memoria, ya sea desde la línea de comandos o desde su suite de pruebas. Esto permite una respuesta rápida a la hora de implementar un comportamiento que no requiere ningún procesamiento. Una vez que hayas desarrollado tu biblioteca, puedes consumirla tal cual desde tu interfaz de usuario.


local agent = require "resty.siteminder.agent"

local smhost = arg[1]
local agentname = arg[2]
local pSmApiHandle = agent.boot(smhost, agentname)

if pSmApiHandle ~= -1 then
    local _, resourceContext, realm = agent.is_protected("/private/index.html",
                                                         "GET", pSmApiHandle)
    local res, session, iNumAttributes, pAttributes =
        agent.login("admin", "secret", resourceContext, realm, pSmApiHandle)
    local res2, token = agent.sso(pSmApiHandle,
                                  "cn=admin,ou=Contoso,o=psdsa,c=US", session,
                                  iNumAttributes[0])
    print("Token: " .. token)
    local res3, attrsnum, tokenAttrs, updatedToken =
        agent.decode_sso_token(pSmApiHandle, token)
    print("User DN: " .. tokenAttrs[agent.SM_AGENTAPI_ATTR_USERDN])
    for akey, aval in pairs(tokenAttrs) do
        if #aval > 0 then
            print(akey .. "=" .. aval)
        end
    end
end

Antes de ejecutar el script, asegúrese de haber configurado el dominio correspondiente en el servidor de políticas. Por ejemplo, "/private/index.html" es uno de los recursos protegidos.

Creación e implementación

Es posible que desee utilizar el siguiente Makefile para simplificar la construcción y el despliegue de nuestros scripts.


OPENRESTY_PREFIX=/usr/local/openresty

LUA_VERSION := 5.3
PREFIX ?=          /usr/local
LUA_INCLUDE_DIR ?= $(PREFIX)/include
LUA_LIB_DIR ?=     $(PREFIX)/lib/lua/$(LUA_VERSION)
INSTALL ?= install

.PHONY: all test install

all: ;

install: all
	$(INSTALL) -d $(DESTDIR)$(LUA_LIB_DIR)/resty/siteminder
	$(INSTALL) lib/resty/siteminder*.lua $(DESTDIR)$(LUA_LIB_DIR)/resty/
	$(INSTALL) lib/resty/siteminder/*.lua $(DESTDIR)$(LUA_LIB_DIR)/resty/siteminder/
	
example: install
	resty -I /usr/local/lib/lua/5.3 examples/sso.lua conf/SmHost.conf spsapacheagent 

test: all
	PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r t

Prueba

Ejecuta el siguiente código desde tu shell:


$ make example

Debe imprimir el DN del usuario y el token de SSO generado.

Conclusiones

Fue todo un viaje. Hemos demostrado cómo se puede aprovechar el SDK de Siteminder para crear tu propia pasarela de acceso sobre software libre, como el moderno servidor web de Nginx, sin tener que reinventar nada, y utilizar scripts de Lua en lugar de tener que lidiar con todos los matices del código nativo, como la gestión manual de la memoria.



__wf_reserved_heredar

Subscribe to our newsletter now!

Thanks for joining our newsletter.
¡Uy! Algo salió mal.