Acaba con las fugas de información en Confluence

¿Sabes cómo crear alertas de seguridad si tu Confluence tiene diferentes tipos de niveles de acceso? Por ejemplo, empleados internos de la empresa y empleados externos, de forma que si un administrador de un espacio de Confluence da demasiados permisos, el sistema pueda detectarlo y alertarnos.

En este ejercicio vamos a crear un pequeño script que nos ayudará a salvaguardar la seguridad de la información de los espacios de nuestro Confluence Server o Datacenter. Para ello crearemos un ‘Job’ en Scriptrunner dentro de nuestro Jira Server o Datacenter. Es necesario Adaptavist Scriptrunner para completar este ejercicio.

Confluence Server y Datacenter pueden ser configurados para tener diferentes niveles de acceso. Por ejemplo, podemos dar un acceso más abierto a las personas internas de nuestra empresa y, por otro lado, podemos tener un acceso más limitado para personas externas o outsourcers.

Para mantener esa seguridad utilizamos los grupos de Confluence.

¿Cuál es el problema?

Los espacios de Confluence se gestionan por sus propios space administrators, los administradores de Confluence se diferencian de su responsabilidad con los space managers. Esto puede llevar a problemas de fuga de información y para ello necesitamos crear mecanismos que nos informen de posibles fugas, de forma que la Oficina de Seguridad de nuestra empresa pueda tomar medidas.

¿Qué necesitamos para empezar?

Para empezar, vamos a crear un script programado (un ‘Job’) que compruebe diariamente si hay alguna fuga en Confluence. Si hay una fuga, el script enviará un correo electrónico a la Oficina de Seguridad y también enviará un mensaje a un canal de Slack. En este ejercicio verás 2 formas de hacer llamadas REST al exterior y también verás cómo enviar un email desde código.

Verás dos llamadas REST. En una de ellas usaremos CURL, eso significa que tu Administrador del Sistema Jira debe instalar CURL en el sistema. CURL existe y se utiliza desde 1998.

En la otra llamada REST, enviaremos un mensaje a través de Slack y utilizaremos la clase RESTClient para invocar un WebHook desde nuestro canal de Slack.

Comencemos

Para la receta necesitamos:

  • Un usuario de nivel de acceso limitado (perteneciente al grupo de Confluence de external o outsourcer) para cuando llamemos a CURL ver lo que el usuario puede ver en Confluence: será necesario ‘user:password’ codificado en base64.
  • Conocer la URL de acceso al punto de acceso REST de nuestro Confluence.
  • Un email donde enviaremos la alerta de Seguridad cuando se produzca.
  • Un WebHook de Slack.

Y ahora el código del Job script:

Aquí tienes el script:

import groovy.json.JsonSlurper;
import com.atlassian.jira.issue.comments.CommentManager
import com.atlassian.jira.user.util.UserManager
import com.atlassian.jira.component.ComponentAccessor
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.json.JsonBuilder
import groovy.transform.BaseScript
import groovyx.net.http.ContentType
import groovyx.net.http.HTTPBuilder
import groovyx.net.http.Method
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response

import com.atlassian.jira.config.properties.APKeys
import groovyx.net.http.RESTClient
import groovyx.net.http.HttpResponseDecorator
import groovyx.net.http.URIBuilder

import com.atlassian.mail.Email
import com.atlassian.mail.server.MailServerManager
import com.atlassian.mail.server.SMTPMailServer

MailServerManager mailServerManager = ComponentAccessor.getMailServerManager()
SMTPMailServer mailServer = mailServerManager.getDefaultSMTPMailServer()

def sout = new StringBuilder(), serr = new StringBuilder()
def proc = ""
proc = ("curl -H 'Authorization: Basic BASE64-ENCODED-USER:PASS' -H 'Accept: application/json' -H 'Content-Type: application/json' https://jira.example.com/wiki/rest/spacedirectory/1/search?type=global")
def proc2 = [ 'bash', '-c', proc].execute()
proc2.consumeProcessOutput(sout, serr)
proc2.waitFor();
def result = sout.toString()
def error = serr.toString();
def jsonSlurper = new JsonSlurper()
def seleccion = jsonSlurper.parseText(result)
//TotalSize must be 4
def size = seleccion.totalSize
def spaces = seleccion.spaces.name
log.debug("Size=" + size + " :: " + spaces)
def today = new Date().format('yyyy-MM-dd');
if (size != 2) {
    try {
        log.debug("Sending email to Security Office")
        Email email = new Email("no-reply@example.com")
        email.setMimeType("text/html")
        email.setTo("mraddon@example.com, security-office@example.com")
        email.setSubject("ALERT POSSIBLE CONFLUENCE SPACE LEAK! " + today);
        email.setBody(spaces.toString())
        mailServer.send(email)
    } catch (Exception e) {

    }
    final String webhookURL = "https://hooks.slack.com/services/ZZZ/YYY/XXXX"
    final String channelOrUserId = "#event-alerts"
    def baseUrl = ComponentAccessor.applicationProperties.getString(APKeys.JIRA_BASEURL)
    def message = "ALERT POSSIBLE CONFLUENCE SPACE LEAK! " + today + " " + spaces.toString()
    def client = new RESTClient("https://hooks.slack.com")
    def data = [:]
    
    data.put("channel", channelOrUserId)
    data.put("text", message)
    data.put("iron_emoji", ":ghost:")
    log.debug("Sent to Slack")
    def response = client.post(
        path: new URIBuilder(webhookURL).path,
        contentType: ContentType.HTML,
        body: data,
        requestContentType: ContentType.JSON) as HttpResponseDecorator
    assert response.status == 200 : "Request failed with status $response.status. $response.entity.content.text"
}

Raúl Peláez 1 de Febrero de 2023