Weblogs Código

Picando Código

Actualizaciones en Picando Código: política de privacidad, HTTPS y más

marzo 08, 2021 12:15

Como comenté hace poco, he estado invirtiendo un poco de tiempo en el blog y le he ido mejorando algunas cosas de a poco.

Picando Código en mantenimiento

Entre estas cosas que he ido haciendo, actualicé la Política de Privacidad del sitio. Recientemente eliminé todos los códigos que alguna vez usé por ser un afiliado en Amazon y algún otro servicio online. La mayoría de estos enlaces eran de hace muchos años. En esa época me inscribí en algunos programas de afiliados con la idea de generar ingresos suficientes como para pager el hosting de este sitio. Pero ya no es la idea.

Creo que logré eliminar todos los enlaces de ese tipo, pero si llegan a encontrar alguno, les agradezco me avisen para editarlo. Las respectivas cuentas están cerradas y sigo en mi campaña de evitar y boicotear a Amazon 😬

Con todo eso, eliminé también la sección que hablaba de estos enlaces “patrocinados” con códigos de traqueo en la Política de Privacidad. Los únicos enlaces “comerciales” que quedan son los que van a Humble Bundle, que lo único que incluyen es un parámetro con mi id de afiliado.

También saqué toda referencia a Google Analytics y el uso de cookies de la política de privacidad. Vengo usando Matomo Analytics, un gestor de estadísticas de visitas ético y que respeta la privacidad de los usuarios. Las cookies para trackear a los usuarios que visitan el sitio están desactivadas. Así que por lo menos por mi parte, no tengo nada para trackear las visitas ni uso para las cookies de los usuarios. No tengo forma de identificar a las personas que visitan el sitio.

Otro tema que vengo viendo es de siempre servir las páginas y recursos en HTTPS, incluso cuando alguien entra por un enlace http al sitio. Creo que eso también está arreglado, y si entramos a cualquier página con https://picandocodigo.net o http://picandocodigo.com, el servidor nos va a redirigir a la versión con https.

Por otra parte, estuve trabajando un poco en el rendimiento del servidor y algunos archivos JS y CSS están siendo servidos con menos pedidos http o en modo asíncrono. Noté que algunos archivos de distintos plugins o funcionalidades no funcionaban perfectamente al principio y tuve que corregir eso. Creo que ya está todo corregido, pero si llegan a encontrar algún detalle o error en JavaScript o CSS, agradezco me avisen 🙂

Otra cosa que recuerdo haber corregido es un error del estilo CSS que venía arrastrando desde la última actualización del tema (¡que ya cumplió su primer año!). Cuando se entraba en una página o post con los comentarios deshabilitados, la barra lateral derecha se corría para abajo del contenido 🤦‍♂️. Pero finalmente lo corregí así que las páginas se deberían ver bien.

Quería compartir algunos de los pequeños cambios que de repente no son tan visibles, pero está bueno que estén. Voy a seguir arreglando y mejorando lo que vaya viendo, y de repente agregando cosas, tengo ideas para más huevos de pascua…

El post Actualizaciones en Picando Código: política de privacidad, HTTPS y más fue publicado originalmente en Picando Código.

» Leer más, comentarios, etc...

Picando Código

Resumen DATA Uruguay 2020

marzo 08, 2021 11:30

D.A.T.A.DATA publicó un resumen de lo que fue el año 2020 para la organización. En ese mismo post podemos ver una línea de tiempo detallada con todo el trabajo realizado a lo largo del año. Pero destacan particularmente algunos proyectos y novedades para este año. En 2020 su alcance no se limitó a Uruguay y llegaron a replicar proyectos en México y Colombia.

Comparto las novedades:

ATuNombre.uy ❤ Mujeres con Calle

En marzo se relanzó el proyecto Mujeres con Calle, integrando el trabajo desarrollado por DATA, Mirá Mamá y un hermoso grupo de voluntarias/os en ATuNombre.uy. Para el proyecto nacido en 2015 y renovado en 2017, fue el hito que faltaba para pasar de la concientización al impacto, porque pudimos integrar un llamado a propuestas de nombres para calles que se realizó a través de Montevideo Decide. Lo que durante años fue llamar la atención sobre un tema, pasó a ser transformar esa realidad 💪.

De yapa, este proyecto fue seleccionado y recibió mención en la Bienal Iberoamericana de Diseño 2020, y replicado en la ciudad de Veracruz (México). Ah, y además estamos trabajando con UDELAR para llevar esta idea a la Universidad… 🔥

Uruguay Leaks

Junto a la diaria y PODER lanzamos este sitio basado en Global Leaks, para permitirle a personas que tienen acceso a información de interés público, hacer llegar esa información a la prensa protegiendo su identidad y de forma segura. Es un proyecto diferente a lo que solemos hacer, que hubiese sido imposible sin el apoyo de un grupo de gente maravilloso en Uruguay, México e Italia. Ya ha mostrado sus primeros resultados y esperamos que con el tiempo se siga fortaleciendo.

Caminos de Pacientes

En un trabajo que todavía no se ve, pero que implicó mucho esfuerzo el año pasado, cerramos la primera etapa de un proyecto que esperamos lanzar este año. Es la primera vez que trabajamos con miembros de las asociaciones de pacientes (ATUR y Dame Tu Mano) y contamos con el apoyo de Roche Uruguay. Toda una experiencia a nivel del proceso de co-creación con sectores de la sociedad civil que hasta ahora nunca habíamos trabajado, y con un modelo de financiamiento y sostenibilidad que nos permite diversificar (y por ende hace más sostenible a DATA Uruguay).

¿Dónde Reciclo?

En 2020 dedicamos mucho, mucho, MUCHO tiempo a ¿Dónde Reciclo? y no podríamos estar más felices y orgullosos con el resultado. Desde principios del año estuvimos trabajando con CEMPRE en la versión 3.0 que lanzamos el 31 de julio. Ésta trajo una aplicación completamente renovada, y con muchísimas mejoras. Y la respuesta de usuarios/as y actores interesados fue simplemente increíble; las descargas de la app y sesiones web de éstos meses superaron a todas las anteriores desde 2013 a 2020 🤯, mientras que la cantidad de contenedores y programas se disparó gracias al interés despertado en nuevos actores y programas.

Para DATA fue además una oportunidad de poner en acción un largo proceso de repensar y ordenar cómo trabajamos, definiendo un “stack” de tecnologías que a partir de ahora aplicamos a todo lo que hacemos, procesos internos, documentación y más. El aumento de la eficacia y la eficiencia es notorio y nos permitió -entre otras cosas- convertir el proyecto en uno regional en apenas un par de meses y lanzar en diciembre la misma herramienta para Colombia 🇨🇴, junto a CEMPRE Colombia.

DATYSOC (Fondo Indela)

A veces hacemos cosas interesantes, y a veces tenemos la suerte de facilitarle a otras personas hacer cosas. Ese es el caso con DATYSOC, un proyecto sobre derechos digitales que ya habíamos “incubado” en 2016 y el año pasado tomó un nuevo impulso con un grupo genial de activistas y el apoyo del Fondo INDELA de Fundación Avina y Luminate.

Era sumamente necesario contar en Uruguay con sociedad civil trabajando esos temas (lo sabemos especialmente porque muchas veces esa demanda caía en DATA cuando no tenemos los conocimientos necesarios), y la prueba más fehaciente es que probablemente hayan visto ya a más de una de las integrantes de DATYSOC enriqueciendo la discusión sobre temas de vigilancia, privacidad, seguridad digital, acoso en línea y más este año 👏.

Por Mi Barrio en Río Negro

Después de un proceso largo y con varios tropezones, nuestra herramienta llegó al litoral de la mano de la Intendencia de Río Negro y con apoyo de ANII.

Talleres de la Red de Gobierno Abierto para el 5º Plan de Acción Nacional de Gobierno Abierto

Desde que la RGA se formó en 2014, las organizaciones que la formamos venimos apoyando el proceso de gobierno abierto de forma principalmente voluntaria. En 2020 logramos acceder por primera vez a un fondo internacional para apoyar el proceso de co-creación del próximo plan de acción (que esperamos se anuncie muy pronto) y eso nos permitió armar un gran equipo para llegar a organizaciones sociales en todo el país, informar sobre el proceso, capacitar y acompañar a lo largo de todo el proceso de co-creación del plan.

El primer paso fue una serie de talleres vía Zoom, donde logramos más de 100 inscripciones, logrando una cantidad de organizaciones, de todo el país y de una diversidad de temas que nunca habíamos logrado hasta ahora. Es un comienzo muy auspicioso, y viene mucho, mucho más trabajo este año que esperamos que redunde en un plan de acción mas representativo y participativo que nunca.

Esperamos que este resumen ayude a hacerse una idea de lo que DATA Uruguay logró en 2020, aunque nos haya quedado por fuera mucho de lo que hacemos y no es tan visible 😅.

Como siempre, muchísimas gracias por acompañarnos y que 2021 nos encuentre trabajando juntos.

Visita el sitio de DATA o seguilos en Twitter.

El post Resumen DATA Uruguay 2020 fue publicado originalmente en Picando Código.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 434

marzo 08, 2021 07:05

Enlaces interesantes

Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Una sinfonía en C#

Observable en Javascript y RxJs, operators

marzo 08, 2021 12:00

En el post anterior contamos un poco qué son los observable y cómo funcionan en RXjs. Ahora vamos a ir un paso más allá y jugar un poco con uno de sus principales elementos, los operadores.

¿Qué es un operator?

Es una función, es un tanto complejo si queremos ver su definición pero para simplificarlo digamos que una observable puede ser procesado de algún modo y el resultado retorna como otro observable. Del mismo modo que podemos filtrar o hacer un mapping una colección y retornar otra colección igual (aunque vamos a ver que los operators pueden hacer mucho más que eso)

Primer contacto con un operator

Los primer es saber cómo usamos un operator, pongamos un ejemplo, digamos que queremos solo leer el primer click que se hace sobre la pantalla (sé que hay modos más simples, es solo un ejemplo) Bien, podemos usar un obsevable para escuchar esos evento, del siguiente modo:

const buttonClickObs = fromEvent(document, "click");
let $dockObs = buttonClickObs.subscribe(data=>console.log(data));

Esto funciona, pero escucha el primero todos los eventos, nunca deja de escuchar el stream; más allá de que podamos en el liberar el observable desuscribiendonos así:

$dockObs.unsuscribe();

Sin embargo no podemos hacer esto justo después de un click, pero podemos usar un operador.

buttonClickObs.pipe(take(1)).subscribe(observer);

el método pipe permite agregar operadores al flujo de procesamiento del stream original, en este ejemplo pasamos la función (el operador) take pero podríamos pasar varios, y se irían procesando uno tras otro, es decir, el resultado del observable se pasa el primer operador, el resultado de ese operador al segundo, etc. el resultado final se retorna a los suscriptores.

Este operador take toma una cantidad de elementos y finaliza la suscripción, podemos ver que nos devuelve el primer click y luego se ejecuta el complete.

Más operadores

Veamos otros de los más simples, en este caso skip

buttonClickObs.pipe(skip(2)).subscribe(observer);

A diferencia del take skip ignora los primeros n elementos y a partir de ahí retorna todos los siguientes, por supuesto que podríamos utilizarlos juntos, para tomar solo el tercer y el cuarto elemento del observable por ejemplo.

buttonClickObs.pipe(skip(2), take(2)).subscribe(observer);

También podemos hacer esto

buttonClickObs.pipe(skip(2)).pipe(take(2)).subscribe(observer);

Veamos otros

filter

Filter nos permite pasar una función que es el criterio de filtrado, igual que el filter de array en Javascript

var obsevable = from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
obsevable.pipe(filter(i=>i>5)).subscribe(observer);

map

Maps es, de nuevo, muy similiar al método de array, pasamos una función con la cual proyectamos el resultado. En este caso multiplicamos por dos cada elemento.

from([1,2,3,4,5,6,7,8,9,0]).pipe(map( (i)=> i * 2)).subscribe(observer)

Por supuesto, podemos combinarlos

from([1,2,3,4,5,6,7,8,9,0]).pipe(skip(2)).pipe(map( (i)=> i * 2)).subscribe(observer)

tap

Tap es interesante, porque permite leer los elementos pero no modifica el resultado que se pasa como resultado es útil, por ejemplo, para logging

from([1,2,3,4,5,6,7,8,9,0]).pipe(tap((i)=>console.log("TAP: " + i * 2))).subscribe(observer)

otros similares

  • while
  • skipWhile
  • takeUntil
  • select

delay

Delay espera un tiempo antes de comenzar a retornar los resultados

from([1,2,3,4,5,6,7,8,9,0]).pipe(delay(5000)).subscribe(observer)

DebunceTime

// no retorna nada hasta que pasa un tiempo desde que no llegan valores, es decir, si estamos escuchando los eventos en un input podemos decirle que no emita nada hasta que no haya pasado un tiempo sin que se escriba algo.

let inputevent = fromEvent<any>(document.querySelector("input"), "input");
inputevent.pipe(map(i=>i.target.value)).pipe(debounceTime(400)).subscribe(observer);

DistinctUntilChange

// filtra los valores iguales

const source$ = from([1, 1, 2, 2, 3, 3]);

source$
  .pipe(distinctUntilChanged())
  .subscribe(observer);

En este caso solo devolera 1,2,3

Podemos pasar una función que será el criterio para determinar que dos elementos son iguales, ya que por defecto compara referencias (Es decir que con objetos no funciona a menos que indiquemos cómo hacerlo con una función)

Bien, hemos visto bastantes, faltan ver algunos más complejos y bastante útiles, pero lo dejamos para la próxima. Nos leemos.

» Leer más, comentarios, etc...

Header Files

Visitando colecciones de una misma clase base

marzo 07, 2021 04:20

Se dice que uno de los grandes defectos de los programadores es que somos perezosos, ya que no nos gusta tener que hacer las cosas más de una vez: cuando eso ocurre creamos un script, separamos en una función, hacemos una aplicación. Como extensión, no nos gusta reinventar la rueda: ¿por qué lo voy a hacer una vez si ya alguien lo ha hecho antes? Esto se llama reutilización de código, y puede venir en forma de bibliotecas de funciones, bibliotecas, frameworks (o entradas de blogs que nos expliquen las cosas 😉). Existen además muchos problemas recurrentes en los que la solución no es un código específico sino la manera de abordar el problema, donde todas las soluciones tienen la misma forma, el mismo patrón de diseño. Existen multitud de patrones de diseño, pero hoy nos centraremos en una variante del patrón visitor (para más información sobre otros patrones recomiendo la lectura de un clásico).

De forma resumida, el patrón visitor aplica, a unos o más objetos de clases heterogéneas, una función que es específica para ese objeto (aunque la función no necesariamente pertenezca al objeto).

std::visit

La biblioteca estándar de C++ (17) proporciona la función std::visit, que se asocia siempre a la clase std::variant. std::visit ejecuta la versión correcta de la función visitante depeniendo del tipo de dato almacenado (recordad que un variant es una clase cuyo valor puede ser de distintos tipos de datos).

std::variant<int, std::string, double> val = 3.141592;

std::visit(overloaded {
  [](int x) { std::cout << "int\n"; },
  [](const std::string& x) { std::cout << "string\n"; },
  [](double x) { std::cout << "double\n"; },
});

De forma análoga, se podría iterar sobre un vector de variants:

for (auto &v : vec) {
  std::visit( /* ... */ );
}

Una característica de la implementación del std::variant es que los tipos de datos disponibles se definen en tiempo de compilación. Si queremos algo más de flexibilidad, podemos usar la tradicional herencia para utilizar cuantos tipos específicos queramos: todos los objetos heredan de un tipo base sobre el que se definen todas las operaciones comunes. Desgraciadamente esto no siempre puede hacerse ya que no pueden preveerse todas las acciones a realizarse sobre los objetos, ni sería conveniente ya que la complejidad final sería abrumadora.

Aun así, es posible diseñar una variante del patrón visitor que sea más flexible en cuanto a tipos de datos. Más en concreto, esta solución aplica sobre una colección de objetos que heredan de la misma clase pero donde no es necesario definir las acciones concretas de una clase hija como función virtual de la clase base.

filtered_visit

Una diferencia entre el patrón visitor tradicional y el que propongo en este artículo, es que el patrón tradicional es capaz de aplicar una acción específica dependiendo del tipo de dato, mientras que en este caso es más bien una ejecución filtrada por el tipo de dato que soporte la acción solicitada (aunque puede actuar como en el primer caso usando métodos polimórficos).

Ejemplo de clases

Veamos una estructura de clases de ejemplo:

// Worker classes
struct BaseWorker {
  explicit BaseWorker(int value_) : value{value_} {}
  virtual ~BaseWorker() {}
  
  virtual void run() = 0;
  
  int value = 0;
};

struct Worker1 : BaseWorker {
  using BaseWorker::BaseWorker;
  
  void run() override {
    std::cout << "Worker1::run " << value << '\n';
  }
  
  void convert(const std::string& path) {
    std::cout << "Converting " << value << " to " << path << '\n';
  }
};

struct Worker2 : BaseWorker {
  using BaseWorker::BaseWorker;

  void run() override {
    std::cout << "Worker2::run " << value << '\n';
  }
  
  void print() {
    std::cout << "Printing " << value << '\n';
  }
};

std::vector<BaseWorker*> workers;

Como vemos, existen métodos comunes a todos los objetos (BaseWorker::run), y otros que no lo son (Worker1::convert, Worker2::print). Además, pudiese darse el caso de tener que realizar acciones sobre una clase no contempladas en el diseño de la misma (por ejemplo, para destruir todos los objetos al terminar su uso, o guardar una copia de los objetos de tipo Worker1). En consecuencia, el objetivo es diseñar una forma de visitar todos los objetos del contenedor aplicando la misma acción a todos los tipos compatibles (excluyendo de dicha visita a los tipos incompatibles).

Pre-requisitos

Lo primero que tenemos que hacer es poder distinguir el tipo exacto sobre el que actuaremos. Después, necesitaremos diferenciar el tipo de acción a ejecutar sobre el objeto: si es una función (incluyendo lambdas, std::function y cualquier functor en general) o un método de una clase. El siguiente template nos dará el 75% de esa información: si se trata de un método miembro y, en ese caso, de qué clase es, o si es una función (créditos):

template<typename T>
struct ClassOf
{
  using type = void;
};
template<typename Return, typename Class>
struct ClassOf<Return(Class::*)>
{
  using type = Class;
};

En el caso de las funciones, necesitaremos saber el tipo sobre el que actuará. Para ello vamos a suponer que se recibirá el objeto como primer argumento (algo bastante lógico y fácil de establecer como regla). El siguiente fragmento nos devuelve el tipo de datos del primer argumento (créditos):

template<typename Ret, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(*) (Arg, Rest...));

template<typename Ret, typename F, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(F::*) (Arg, Rest...));

template<typename Ret, typename F, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(F::*) (Arg, Rest...) const);

template <typename F>
decltype(first_argument_helper(&F::operator())) first_argument_helper(F);

template <typename T>
using first_argument = decltype(first_argument_helper(std::declval<T>()));

Implementación final

Por último, sólo nos queda la función filtered_visit que recorrerá el contenedor:

template<typename T, typename F, typename... Ts>
void filtered_visit(T&& cont, F f, Ts... args)
{
  using Class = typename ClassOf<F>::type;
  if constexpr (std::is_void_v<Class>) { // it is a function, lambda or std::function
    for (auto &obj : cont) {
      if (auto t = dynamic_cast<first_argument<F>>(obj)) { f(t, args...); }
    }
  } else { // it is a pointer to member
    for (auto &obj : cont) {
      if (auto t = dynamic_cast<Class *>(obj)) { (t->*f)(args...); }
    }
  }
}

Su uso sería el siguiente:

namespace {
  void foo(BaseWorker* obj) {
    std::cout << "foo " << obj->value << '\n';
  }
}

int main() {
  std::vector<BaseWorker*> workers = {
    new Worker1{10},
    new Worker2{20},
    new Worker1{30},
  };
  
  filtered_visit(workers, &BaseWorker::run); // 'run' on all elements
  filtered_visit(workers, &Worker1::convert, "path"); // 'convert' on elements of type 'Worker1' with one argument
  filtered_visit(workers, &Worker2::print); // 'print' on elements of type 'Worker2'
  filtered_visit(workers, [](Worker1* obj) { obj->value += 5; }); // lambda on elements of type 'Worker1'
  filtered_visit(workers, &BaseWorker::run);
  filtered_visit(workers, &foo); // global function (on all elements in this case)
  
  filtered_visit(workers, [](BaseWorker* obj) { delete obj; }); // lambda on all elements
}

👨🏻‍💻El código completo se puede probar en vivo en Wandbox.

Conclusiones

El patrón visitor es ampliamente utilizado en muchos diseños ya que simplifica el tratamiento de datos heterogéneos. Con esta variante podemos expandir su uso a otros escenarios donde se requieren acciones específicas sobre un determinado sub-tipo de elementos de una colección.

Comentarios finales

Una de las limitaciones de este visitor es que no puede aplicar un método de la clase base a un único tipo heredado, ya que las reglas de deducción utilizadas llevarán a la clase base. En este caso basta con utilizar un lambda.

Por otro lado, habréis visto que uso dynamic_cast para determinar si un objeto del contenedor es del tipo que soporta el visitor. Bien se podría cambiar por algún método estático de cada clase que devuelva un identificador de tipo y compararlo con el que devuelve la clase del visitor, o cualquier otra estrategia de identificación de tipos en tiempo de ejecución.

» Leer más, comentarios, etc...

Blog Bitix

Acceso simple y seguro a sistemas remotos con Boundary

marzo 05, 2021 02:00

Boundary es otra herramienta de HashiCorp dedicada a la seguridad. Vault está centrada en el almacenamiento de secretos, Boundary está centrada en otro aspecto de la seguridad que es el acceso a sistemas remotos. Una herramienta mejor adaptada y teniendo en cuenta las propiedades dinámicas de los sistemas actuales. Es una alternativa a los métodos de VPN, bastión y firewall que se utilizan para permitir estos accesos remotos.

HashiCorp Boundary

HashiCorp

El acceso de forma remota a sistemas es necesario y útil más cuando las máquinas ya no son máquinas físicas sino que están en centros de datos en diferentes ubicaciones en el mundo e incluso son máquinas lógicas como las máquinas virtuales ejecutadas en la computación de la nube.

El acceso a las máquinas no solo se requiere para las aplicaciones de forma automatizada sino que en algunas situaciones se requiere acceso manual para realizar tareas administrativas o conexión con clientes específicos como escritorio remoto o clientes de bases de datos.

Permitir el acceso remoto es necesario, sin embargo, requiere seguridad de forma que únicamente los usuarios autorizados accedan únicamente a los sistemas autorizados.

Contenido del artículo

Funcionamiento acceso remoto tradicional

La forma habitual de proporcionar acceso remoto a sistemas de una red privada es mediante una VPN o a través de un sistema intermediario denominado bastión. Estos conceptos proporcionan acceso remoto en ciertos aspectos de forma segura pero no están exentas de problemas e inconvenientes.

El primer problema es que una vez conectado a una VPN o un bastión se tiene acceso a cualquier ordenador de la red privada a la que uno se ha conectado. Un usuario que necesite acceso a una máquina potencialmente tiene conectividad con otros sistemas en esa red, lo que es riesgo de seguridad por permitir la posibilidad de accesos a sistemas no autorizados.

El segundo problema es que las infraestructuras actuales de computación en la nube no son estáticas sino que son dinámicas en las que los sistemas cambian de dirección IP o se crean y destruyen instancias.

Mediante un firewall ubicado entre el bastión y el sistema de acceso remoto es posible limitar a que sistemas de la red es posible conectarse. Sin embargo, un firewall constituye un elemento más a administrar que añade más complejidad en la infraestructura.

Boundary

Casos de uso de Boundary

Boundary

Acceso remoto con Boundary

Para tratar de solventar los problemas de acceso remoto con VPN, bastión y firewall, simplificar la infraestructura y una administración de más alto nivel lógico de usuarios, permisos y sistemas HashiCorp ha publicado otra herramienta dedicada a la seguridad específica para el acceso remoto.

Boundary es una herramienta adaptada a los entornos modernos de computación en la nube y dinámicos agnóstica de la infraestructura y de las aplicaciones utilizadas por los clientes remotos. Su funcionamiento es similar al de un bastión con una diferencia importante de que Boundary es el que realiza la conexión a los equipos remotos y no la aplicación cliente.

Boundary gestiona el inventario de hosts, usuarios, grupos y roles y permisos con los que es capaz de permitir o denegar el acceso únicamente a los hosts permitidos y denegar el acceso a los hosts para los que no se dispone acceso. Funciona en modo cliente y servidor, el servidor hace de bastión y es el que realmente se conecta al sistema remoto, en el modo cliente proporciona a la aplicación cliente conexión con el servidor, a diferencia de un sistema con un bastión o VPC la aplicación cliente no se conecta con el sistema remoto sino que únicamente se conecta al servidor de Boundary.

Boundary es un único binario tanto cuando actúa en modo servidor como en modo cliente, dispone una interfaz de línea de comandos y una interfaz web. En las primeras versiones no están todas la funcionalidades planificadas en el roadmap, a medida que madure el servicio ganará en funcionalidades y utilidad. Integrando Boundary con Consul tendrá acceso al inventario de servicios y hosts. Integrando Boundary con Vault en el momento de acceso al sistema remoto Vault será capaz de crear credenciales de forma dinámica y efímeras.

En su modo servidor requiere de una base de datos PostgreSQL que inicia como un contenedor de Docker. La consola de administración se inicia en el puerto 9200 y el usuario y contraseña se emite en la salida de la terminal al iniciar la herramienta.

Consola de administración de Boundary Consola de administración de Boundary

Consola de administración de Boundary

Conceptos

Boundary tiene varios conceptos de dominio, por un lado está el catálogo o inventario de hosts, un conjunto de hosts agrupados en un host set y estos a su vez en un host catalog. Por otro lado, están los usuarios, grupo y roles. Finalmente los targets son la abstracción del elemento al que se quiere realizar una conexión, el target se traduce en un host. Finalmente, todos estos elementos se agrupan en ámbitos o scopes y proyectos o projects.

En una organización con unas cuantas decenas de sistemas y usuarios, la gestión de este inventario se vuelve ciertamente complicado.

Clientes soportados

Boundary incorpora varios clientes en su modo de funcionamiento como cliente. Estos son:

  • ssh: por defecto es el cliente SSH local (ssh)
  • postgres: por defecto el del cliente oficial de línea de comandos de Postgres (psql)
  • rdp: por defecto es el cliente de Windows RDP (mstsc)
  • http: por defecto es curl
  • kube: por defecto es kubectl

Boundary soportar estos clientes de forma nativa sin embargo es agnóstico de la herramienta cliente, con la opción exec es posible utilizar cualquier herramienta que se conecte con un sistema remoto.

Ejemplo de conexión SSH con Boundary

En el siguiente ejemplo muestro como conectarse a un host a través de Boundary con SSH. En el mismo ejemplo muestro como usar Boundary con autenticación con claves SSH y como usar Boundary con Vault que generar un OTP para realizar la misma conexión sin usar claves SSH y únicamente con credenciales temporales. En este ejemplo el host remoto es una Raspberry Pi con sistema operativo Raspberry Pi OS instalado y con el servicio de SSH iniciado.

En este ejemplo el servidor de Boundary y Vault están en la misma máquina. Los pasos del ejemplo para usar Boundary y SSH, los pasos que involucran ORP son opcionales si se usan claves SSH:

  • Iniciar el servidor de Boundary.
  • Configurar el servidor SSH para que valide los OTP conectándose a Vault.
  • Iniciar Vault y permitir su acceso al host remoto para validar los OTP.
  • Realizar la conexión SSH con Boundary desde la máquina local a la máquina remota.

Iniciar el servidor Boundary es sencillo, basta instalar el binario de Boundary en el PATH del sistema e instalar el gestor de contenedores Docker previamente. Al iniciar el servidor de Boundary este descarga la imagen de Postgres e inicia su contenedor.

1
2
$ sudo systemctl start docker.service

start-docker.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ vault server -dev -dev-listen-address "0.0.0.0:8200"
==> Vault server configuration:

             Api Address: http://0.0.0.0:8200
                     Cgo: disabled
         Cluster Address: https://0.0.0.0:8201
              Go Version: go1.15.8
              Listener 1: tcp (addr: "0.0.0.0:8200", cluster address: "0.0.0.0:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
               Log Level: info
                   Mlock: supported: true, enabled: false
           Recovery Mode: false
                 Storage: inmem
                 Version: Vault v1.5.7
             Version Sha: 81d55e35dbbe844a6feb4f52a9a3d072c052d228+CHANGES

WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
and starts unsealed with a single unseal key. The root token is already
authenticated to the CLI, so you can immediately begin using Vault.

You may need to set the following environment variable:

    $ export VAULT_ADDR='http://0.0.0.0:8200'

The unseal key and root token are displayed below in case you want to
seal/unseal the Vault or re-authenticate.

Unseal Key: oqt+hwz0gOD9nxz8gc9C79B0M6mDx7rsbJelYZDsg5I=
Root Token: s.PAPTxv16g0o9amLr82NYcxBT

Development mode should NOT be used in production installations!

==> Vault server started! Log data will stream in below:

2021-03-05T15:07:47.964+0100 [INFO]  proxy environment: http_proxy= https_proxy= no_proxy=
2021-03-05T15:07:47.964+0100 [WARN]  no `api_addr` value specified in config or in VAULT_API_ADDR; falling back to detection if possible, but this value should be manually set
start-vault.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$ boundary dev
==> Boundary server configuration:

        [Controller] AEAD Key Bytes: A7NiMQ0fNlBiCpFxMxg21k8TAdSNcui/okJGxBg6eZs=
          [Recovery] AEAD Key Bytes: H4U5vaCh1ZmgrRJXhlljvOqkLDzU9ZNy4Cn9Z4+a7Bo=
       [Worker-Auth] AEAD Key Bytes: rWZi4UxADHAlsqbhemdM6YIIyFG9+AuqIcCbAOZILtQ=
               [Recovery] AEAD Type: aes-gcm
                   [Root] AEAD Type: aes-gcm
            [Worker-Auth] AEAD Type: aes-gcm
                                Cgo: disabled
     Controller Public Cluster Addr: 127.0.0.1:9201
             Dev Database Container: confident_mcnulty
                   Dev Database Url: postgres://postgres:password@localhost:49153/boundary?sslmode=disable
         Generated Admin Login Name: admin
           Generated Admin Password: password
           Generated Auth Method Id: ampw_1234567890
          Generated Host Catalog Id: hcst_1234567890
                  Generated Host Id: hst_1234567890
              Generated Host Set Id: hsst_1234567890
             Generated Org Scope Id: o_1234567890
         Generated Project Scope Id: p_1234567890
                Generated Target Id: ttcp_1234567890
  Generated Unprivileged Login Name: user
    Generated Unprivileged Password: password
                         Listener 1: tcp (addr: "127.0.0.1:9200", cors_allowed_headers: "[]", cors_allowed_origins: "[*]", cors_enabled: "true", max_request_duration: "1m30s", purpose: "api")
                         Listener 2: tcp (addr: "127.0.0.1:9201", max_request_duration: "1m30s", purpose: "cluster")
                         Listener 3: tcp (addr: "127.0.0.1:9202", max_request_duration: "1m30s", purpose: "proxy")
                          Log Level: info
                              Mlock: supported: true, enabled: false
                            Version: Boundary v0.1.7
                        Version Sha: bc565922fbd3a18c9f6a22cd2e80a93df0d7cd45
           Worker Public Proxy Addr: 127.0.0.1:9202

==> Boundary server started! Log data will stream in below:

2021-03-05T15:08:05.051+0100 [INFO]  worker: connected to controller: address=127.0.0.1:9201
2021-03-05T15:08:05.054+0100 [INFO]  controller: worker successfully authed: name=dev-worker
2021-03-05T15:08:05.059+0100 [WARN]  worker: got no controller addresses from controller; possibly prior to first status save, not persisting
start-boudary.sh
1
2
3
$ docker ps -a
CONTAINER ID   IMAGE         COMMAND                  CREATED       STATUS       PORTS                     NAMES
e48475a774bb   postgres:11   "docker-entrypoint.s…"   2 hours ago   Up 2 hours   0.0.0.0:49153->5432/tcp   confident_mcnulty
docker-ps.sh

Conexión SSH a sistema remoto

Realizando la conexión directamente al host remoto con SSH se realiza con el siguiente comando, esta es la forma tradicional de hacerlo usando claves SSH.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[picodotdev@archlinux ~]$ ssh pi@192.168.1.101
Linux raspberrypi 5.10.11+ #1399 Thu Jan 28 12:02:28 GMT 2021 armv6l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Feb 25 21:21:31 2021 from 192.168.1.7
[pi@raspberrypi ~]$
ssh.sh

Utilizando Boundary como intermediario estos son los comandos. Primero hay que realizar la autenticación en Boundary y luego usar Boundary para que establezca la conexión SSH con el host destino, previamente hay es necesario haber configurado los recursos en Boundary.

1
2
3
4
5
6
7
8
$ boundary authenticate password -auth-method-id=ampw_1234567890 -login-name=admin -password=password
Authentication information:
  Account ID:      apw_cxGWZ0KOIc
  Auth Method ID:  ampw_1234567890
  Expiration Time: Fri, 12 Mar 2021 15:21:11 CET
  Token:           at_1PE5BRB96M_s18pNSYQHzzoZmPD8eyuUyEpqiZw5VFBvAPN7bs2MUamBpdY316whSfZthEVw3KED7khERfPxRTKAm8TH14vNAWW7UrCamxSgYToYBQ3x7kxJHPfeZdtEwhfwx7NZ8TWaXVL2gdppJ4sZ
  User ID:         u_1234567890
$ export BOUNDARY_TOKEN="at_1PE5BRB96M_s18pNSYQHzzoZmPD8eyuUyEpqiZw5VFBvAPN7bs2MUamBpdY316whSfZthEVw3KED7khERfPxRTKAm8TH14vNAWW7UrCamxSgYToYBQ3x7kxJHPfeZdtEwhfwx7NZ8TWaXVL2gdppJ4sZ"
boundary-authentication.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[picodotdev@archlinux ~]$ boundary connect ssh -target-id ttcp_23T9SbQ7ce -username pi -- -i "~/.ssh/pico.dev@gmail.com"
Linux raspberrypi 5.10.11+ #1399 Thu Jan 28 12:02:28 GMT 2021 armv6l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri Mar  5 15:12:56 2021 from 192.168.1.4
[pi@raspberrypi ~]$ 
ssh-boundary-1.sh

Utilizar Boundary como intermediario requiere configurar la herramienta para que tenga un inventario de host a los cuales es posible conectarse y usuarios con sus permisos. En el ejemplo utilizo una Raspberry Pi como host al que realizar la conexión en la dirección IP 192.168.1.101. Para utilizar la linea de comandos de Boundary hay que autenticarse y establecer la variable de entorno BOUNDARY_TOKEN.

Las operaciones de estos comandos para crear los recursos están disponibles también para realizarlas desde su consola web de administración.

Crear recursos de Boundary Crear recursos de Boundary

Crear recursos de Boundary
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
$ boundary scopes create -name bitix-scope -description "Example Bitix scope"

Scope information:
  Created Time:        Fri, 05 Mar 2021 15:24:33 CET
  Description:         Example Bitix scope
  ID:                  o_PuL7bdpoKw
  Name:                bitix-scope
  Updated Time:        Fri, 05 Mar 2021 15:24:33 CET
  Version:             1

  Scope (parent):
    ID:                global
    Name:              global
    Type:              global

  Authorized Actions:
    read
    update
    delete
$ boundary scopes create -name bitix-project -description "Example Bitix project" -scope-id=o_PuL7bdpoKw

Scope information:
  Created Time:        Fri, 05 Mar 2021 15:26:54 CET
  Description:         Example Bitix project
  ID:                  p_MPpTkaalUm
  Name:                bitix-project
  Updated Time:        Fri, 05 Mar 2021 15:26:54 CET
  Version:             1

  Scope (parent):
    ID:                o_PuL7bdpoKw
    Name:              bitix-scope
    Parent Scope ID:   global
    Type:              org

  Authorized Actions:
    read
    update
    delete
$ boundary host-catalogs create static -name bitix-host-catalog -description "Bitix host-catalog" -scope-id=p_MPpTkaalUm

Host Catalog information:
  Created Time:        Fri, 05 Mar 2021 15:30:22 CET
  Description:         Bitix host-catalog
  ID:                  hcst_8t06PaPJHK
  Name:                bitix-host-catalog
  Type:                static
  Updated Time:        Fri, 05 Mar 2021 15:30:22 CET
  Version:             1

  Scope:
    ID:                p_MPpTkaalUm
    Name:              bitix-project
    Parent Scope ID:   o_PuL7bdpoKw
    Type:              project

  Authorized Actions:
    read
    update
    delete

  Authorized Actions on Host Catalog\'s Collections:
    host-setss:
      create
      list
    hostss:
      create
      list
$ boundary host-sets create static -name=raspberrypi -description="Raspbrry Pi host set" -host-catalog-id=hcst_8t06PaPJHK 

Host Set information:
  Created Time:        Fri, 05 Mar 2021 15:58:01 CET
  Description:         Raspbrry Pi host set
  Host Catalog ID:     hcst_8t06PaPJHK
  ID:                  hsst_e6BJkM7PqR
  Name:                raspberrypi
  Type:                static
  Updated Time:        Fri, 05 Mar 2021 15:58:01 CET
  Version:             1

  Scope:
    ID:                p_MPpTkaalUm
    Name:              bitix-project
    Parent Scope ID:   o_PuL7bdpoKw
    Type:              project

  Authorized Actions:
    read
    update
    delete
    add-hosts
    set-hosts
    remove-hosts
$ boundary hosts create static -name raspberrypi -description "Static host for Raspberry Pi" -address "192.168.1.101" -host-catalog-id=hcst_8t06PaPJHK

Host information:
  Created Time:        Fri, 05 Mar 2021 15:38:18 CET
  Description:         Static host for Raspberry Pi
  Host Catalog ID:     hcst_8t06PaPJHK
  ID:                  hst_2eN5yaTE41
  Name:                raspberrypi
  Type:                static
  Updated Time:        Fri, 05 Mar 2021 15:38:18 CET
  Version:             1

  Scope:
    ID:                p_MPpTkaalUm
    Name:              bitix-project
    Parent Scope ID:   o_PuL7bdpoKw
    Type:              project

  Authorized Actions:
    read
    update
    delete

  Attributes:
    address:           192.168.1.101
$ boundary host-sets add-hosts --host=hst_2eN5yaTE41 -id=hsst_e6BJkM7PqR

Host Set information:
  Created Time:        Fri, 05 Mar 2021 15:58:01 CET
  Description:         Raspbrry Pi host set
  Host Catalog ID:     hcst_8t06PaPJHK
  ID:                  hsst_e6BJkM7PqR
  Name:                raspberrypi
  Type:                static
  Updated Time:        Fri, 05 Mar 2021 16:01:36 CET
  Version:             2

  Scope:
    ID:                p_MPpTkaalUm
    Name:              bitix-project
    Parent Scope ID:   o_PuL7bdpoKw
    Type:              project

  Authorized Actions:
    read
    update
    delete
    add-hosts
    set-hosts
    remove-hosts

  Host IDs:
    hst_2eN5yaTE41
$ boundary targets create tcp -name raspberrypi -description "Raspberry Pi target" -default-port=22 -scope-id=p_MPpTkaalUm

Target information:
  Created Time:               Fri, 05 Mar 2021 15:42:04 CET
  Description:                Raspberry Pi target
  ID:                         ttcp_23T9SbQ7ce
  Name:                       raspberrypi
  Session Connection Limit:   1
  Session Max Seconds:        28800
  Type:                       tcp
  Updated Time:               Fri, 05 Mar 2021 15:42:04 CET
  Version:                    1

  Scope:
    ID:                       p_MPpTkaalUm
    Name:                     bitix-project
    Parent Scope ID:          o_PuL7bdpoKw
    Type:                     project

  Authorized Actions:
    read
    update
    delete
    add-host-sets
    set-host-sets
    remove-host-sets
    authorize-session
$ boundary targets add-host-sets -host-set=hsst_e6BJkM7PqR -id=ttcp_23T9SbQ7ce

Target information:
  Created Time:               Fri, 05 Mar 2021 15:42:04 CET
  Description:                Raspberry Pi target
  ID:                         ttcp_23T9SbQ7ce
  Name:                       raspberrypi
  Session Connection Limit:   1
  Session Max Seconds:        28800
  Type:                       tcp
  Updated Time:               Fri, 05 Mar 2021 15:59:03 CET
  Version:                    2

  Scope:
    ID:                       p_MPpTkaalUm
    Name:                     bitix-project
    Parent Scope ID:          o_PuL7bdpoKw
    Type:                     project

  Authorized Actions:
    read
    update
    delete
    add-host-sets
    set-host-sets
    remove-host-sets
    authorize-session

  Host Sets:
    Host Catalog ID:          hcst_8t06PaPJHK
    ID:                       hsst_e6BJkM7PqR
boundary-configuration.sh

Una de las funcionalidades que proporciona Boundary es trazabilidad en el log se muestran los inicio de las conexiones y su finalización, permite ver las conexiones abiertas y forzar el cierre de una conexión.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
2021-03-05T16:31:06.880+0100 [INFO]  controller.worker-handler: authorized connection: session_id=s_S91yFIr4qi connection_id=sc_tg5qgexhhe connections_left=0
2021-03-05T16:31:06.888+0100 [INFO]  controller.worker-handler: connection established: session_id=s_S91yFIr4qi connection_id=sc_tg5qgexhhe client_tcp_address=127.0.0.1 client_tcp_port=40058 endpoint_tcp_address=192.168.1.101 endpoint_tcp_port=22
2021-03-05T16:31:09.001+0100 [INFO]  controller.worker-handler: connection closed: connection_id=sc_tg5qgexhhe
2021-03-05T16:33:29.299+0100 [INFO]  controller.worker-handler: session activated: session_id=s_7yPhGF7hHz target_id=ttcp_23T9SbQ7ce user_id=u_1234567890 host_set_id=hsst_e6BJkM7PqR host_id=hst_2eN5yaTE41
2021-03-05T16:33:29.305+0100 [INFO]  controller.worker-handler: authorized connection: session_id=s_7yPhGF7hHz connection_id=sc_u5dxQjveKv connections_left=0
2021-03-05T16:33:29.314+0100 [INFO]  controller.worker-handler: connection established: session_id=s_7yPhGF7hHz connection_id=sc_u5dxQjveKv client_tcp_address=127.0.0.1 client_tcp_port=40078 endpoint_tcp_address=192.168.1.101 endpoint_tcp_port=22
2021-03-05T16:33:32.302+0100 [INFO]  controller.worker-handler: connection closed: connection_id=sc_u5dxQjveKv
2021-03-05T16:33:34.745+0100 [INFO]  controller.worker-handler: session activated: session_id=s_xmwSyOuFgW target_id=ttcp_23T9SbQ7ce user_id=u_1234567890 host_set_id=hsst_e6BJkM7PqR host_id=hst_2eN5yaTE41
2021-03-05T16:33:34.757+0100 [INFO]  controller.worker-handler: authorized connection: session_id=s_xmwSyOuFgW connection_id=sc_XkYBKBV1J0 connections_left=0
2021-03-05T16:33:34.772+0100 [INFO]  controller.worker-handler: connection established: session_id=s_xmwSyOuFgW connection_id=sc_XkYBKBV1J0 client_tcp_address=127.0.0.1 client_tcp_port=40088 endpoint_tcp_address=192.168.1.101 endpoint_tcp_port=22
boundary.log

Conexiones realizadas con Boundary

Conexiones realizadas con Boundary

Autenticación con SSH usando OTP

Si no se proporciona la clave privada el servidor de SSH y se ha configurado como modo de autenticación alternativo OTP al realizar la conexión se solicita una contraseña temporal de un solo uso u OTP, una vez usado el OTP si se intenta utilizar el mismo código en una autenticación posterior esta falla.

Vault permite generar contraseñas de un solo uso y configurando SSH validar los OTP proporcionados en la conexión. La ventaja de usar OTP es que no es necesario distribuir las claves públicas en cada una de las máquinas en las que se quiera realizar la conexión SSH. Hay que cambiar los archivos de configuración /etc/pam.d/sshd, /etc/ssh/sshd_config, /etc/vault-ssh-helper.d/config.hcl y reiniciar el servicio de SSH, estos cambios permiten al servidor SSH validar los tokens proporcionados conectándose con Vault.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
$ ssh pi@192.168.1.101
$ wget https://releases.hashicorp.com/vault-ssh-helper/0.2.1/vault-ssh-helper_0.2.1_linux_amd64.zip
$ wget https://releases.hashicorp.com/vault-ssh-helper/0.2.1/vault-ssh-helper_0.2.1_linux_arm.zip
$ sudo unzip -q vault-ssh-helper_0.2.1_linux_arm.zip -d /usr/local/bin
$ sudo chmod 0755 /usr/local/bin/vault-ssh-helper
$ sudo chown root:root /usr/local/bin/vault-ssh-helper
$ sudo mkdir /etc/vault-ssh-helper.d/

$ sudo tee /etc/vault-ssh-helper.d/config.hcl <<EOF
vault_addr = "http://192.168.1.4:8200"
tls_skip_verify = false
ssh_mount_point = "ssh"
allowed_roles = "*"
EOF

$ vault-ssh-helper -verify-only -dev -config /etc/vault-ssh-helper.d/config.hcl
==> WARNING: Dev mode is enabled!
[INFO] using SSH mount point: ssh
[INFO] using namespace: us
[INFO] vault-ssh-helper verification successful!
install-ssh-helper.sh
1
2
$ sudo vim /etc/pam.d/sshd

sshd.sh
1
2
3
#@include common-auth
auth requisite pam_exec.so quiet expose_authtok log=/var/log/vault-ssh.log /usr/local/bin/vault-ssh-helper -dev -config=/etc/vault-ssh-helper.d/config.hcl
auth optional pam_unix.so not_set_pass use_first_pass nodelay
sshd
1
2
$ sudo vim /etc/ssh/sshd_config

sshd_config.sh
1
2
3
ChallengeResponseAuthentication yes
UsePAM yes
PasswordAuthentication no
sshd_config

Para validar el OTP el servidor SSH a través del módulo de autenticación PAM le pide a Vault que lo valide, para ello necesita conectividad de red, si es necesario activando la regla del firewall para permitir el tráfico de red para el puerto 8200 que utiliza Vault.

1
2
$ sudo ufw allow 8200/tcp

vault-configuration-firewall.sh

También hay que configurar Vault con las políticas y permisos para permitir a un usuario autenticado generar los OTP de autenticación.

1
2
3
4
5
6
$ export VAULT_ADDR='http://127.0.0.1:8200'
$ vault secrets enable ssh
$ vault write ssh/roles/otp_ssh \
    key_type=otp \
    default_user=pi \
    cidr_list=192.168.1.0/24
vault-configuration.sh

El comando para generar el OTP e iniciar la conexión es el siguiente. Este comando realiza dos acciones, generar el OTP e iniciar la conexión SSH, el OTP hay que copiarlo y pegarlo de forma manual en la solicitud de contraseña que corresponde con el OTP generado.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
[picodotdev@archlinux ~]$ vault ssh -role otp_ssh -mode otp -strict-host-key-checking=no pi@192.168.1.101
Vault could not locate "sshpass". The OTP code for the session is displayed
below. Enter this code in the SSH password prompt. If you install sshpass,
Vault can automatically perform this step for you.
OTP for the session is: 31233d29-e822-6891-6a34-9a101fb2e344
Password:
Linux raspberrypi 5.10.11+ #1399 Thu Jan 28 12:02:28 GMT 2021 armv6l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri Mar  5 15:12:56 2021 from 192.168.1.4
[pi@raspberrypi ~]$ 
ssh-vault-otp-1.sh

Generar el OTP y realizar la conexión se puede realizar en dos pasos separados.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[picodotdev@archlinux ~]$ vault write ssh/creds/otp_ssh username=pi ip=192.168.1.101
Key                Value
---                -----
lease_id           ssh/creds/otp_ssh/7vB38cBFRWl883ePHusCBAMI
lease_duration     768h
lease_renewable    false
ip                 192.168.1.101
key                93e489c2-c30c-6e4e-f22c-96e719d41fd3
key_type           otp
port               22
username           pi
[picodotdev@archlinux ~]$ ssh pi@192.168.1.101
Password:
Linux raspberrypi 5.10.11+ #1399 Thu Jan 28 12:02:28 GMT 2021 armv6l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri Mar  5 15:12:56 2021 from 192.168.1.4
[pi@raspberrypi ~]$ 
ssh-vault-otp-2.sh

Usando Vault la conexión se realiza entre la máquina local y la máquina destino, usando Boundary como intermediario el comando es el mismo que al usar una clave SSH pero al iniciar la conexión se solicita la contraseña OTP.

1
2
$ boundary connect ssh -target-id ttcp_23T9SbQ7ce -username pi

ssh-boundary-2.sh

» Leer más, comentarios, etc...

Picando Código

Siete días en el Picandoverso – Primera semana de Marzo

marzo 03, 2021 12:00

Primeros 7 días en el Picandoverso de Marzo de 2021. Darme cuenta que va a hacer 1 año que no salgo de Edimburgo, pasando por distintos niveles de encierros y estados mentales, me ha pesado un poco esta última semana. Pero seguimos “en la lucha”. Vengo mirando de nuevo Futurama desde que llegó a Disney+ por éstos lados. ¡Qué buena serie! También sigo jugando a los distintos Street Fighter en Nintendo Switch y leyendo cómics. Estos días voy a volver a Super Mario 3D World y seguir recorriendo mi biblioteca de juegos. Estoy esperando con ansias jugar Tony Hawk’s Pro Skater en Nintendo Switch 🛹

El trabajo viene bastante cargado de cosas para hacer. Pero este mes me voy a tomar unos días libres que vengo arrastrando sin tomar del año pasado. Es bueno desconectarse, por suerte en Elastic nos insisten con eso y cuidan nuestra salud mental (en mi caso a veces hasta más de lo que la cuido yo). La próxima versión de los clientes en las que estoy trabajando va a ser la 7.12, y va a haber bastantes actualizaciones para entonces.

Siete días en el Picandoverso – Primera semana de Marzo

Paso a comentar esos enlaces y noticias que fui compilando estos últimos 7 días:

Programación/Tecnología

🎂 ¡Fue el cumpleaños del lenguaje de programación Ruby! El pasado 24 de febrero Matz publicó que Ruby fue nombrado Ruby el 24 de febrero de 1993. Ruby no parece tan viejo, es de esos lenguajes que a pesar de los años mantiene su juventud…

💎 El 31 de marzo termina el soporte para Ruby 2.5, así que hay que ir viendo de actualizarse. Podemos seguir usando 2.6 hasta el 31 de marzo de 2022, así como 2.7 y 3.0.

🌍 Por medio de Planet GNOME me enteré de este creador de UIs para GNOME: Cambalache. Me llamó la atención el nombre, y la animación que muestra su funcionamiento promete.

🧮 Microsoft presentó Power-Fx, un lenguaje de programación low-code basado en formulas del estilo hojas de cálculo. Es fuertemente tipado, declarativo, funcional, con lógica imperativa y gestión de estado disponible cuando es necesario. Publicado bajo licencia MIT.

🦊 El zorrito del logo de Firefox no se va a ningún lado. En una demostración del daño que puede hacer la desinformación online, Mozilla publicó un artículo sobre cómo a raíz de un meme sobre el logo de Firefox, un montón de gente que no usa Firefox empezó a difundir noticias falsas sobre el logo. Conclusión que si no usas Firefox, te terminas volviendo anti-vacunas (?). De yapa para los que usamos Firefox Nightly, volvió el logo de Firefox Doge.

🎮 🐧 Steam Link está ahora disponible para sistemas GNU/Linux mediante flatpak. Es un sistema que nos permite hacer streaming de juegos en nuestra biblioteca de Steam a teléfonos móviles, tables y televisores.

Videojuegos

🎮 Increíblemente, el Wii U recibió una actualización de firmware por primera vez desde 2018. Como nos tenía acostumbrado Nintendo a los usuarios de dicha consola, este firmware trae mejoras a la usabilidad y estabilidad del sistema. A veces extraño mi Wii U, desde 2017 está guardado en una caja en un depósito con el resto de mis queridas consolas Nintendo. De todas formas van quedando pocos juegos en ese sistema que no hayan sido publicados también en Nintendo Switch, pero era una linda maquinita y tuve muy buenas sesiones jugando con amigos.

🐭⚡ Celebrando el 25 aniversario de Pokémon, se anunciaron títulos nuevos para Nintendo Switch. Los primeros dos son remakes de Pokémon Diamond y Pearl, publicados originalmente en 2006 para Nintendo DS. Los remakes van a llamarse Pokémon Brilliant Diamond y Pokémon Shining Pearl y van a estar disponibles a fines de año. A diferencia de Pokémon Let’s Go, usa una estética nueva, con gráficos basados en el original pero un poco mejorados para aprovechar más las capacidades del Switch.

YouTube Video

El siguiente juego fue Pokémon Legends: Arceus. Éste me resultó más interesante. Tiene pinta de ser algo más parecido  a lo que los fans de Pokémon han estado pidiendo por años. Un sistema más aventura y con un mundo más “abierto”. Da mucho un aire Legend Of Zelda: Breath of the Wild, pero habrá que esperar a más adelante para tener más información. Estará disponible a principios de 2022:

YouTube Video

Películas / TV

🕸 Marvel reveló el título de la próxima película de Spider-Man. Será “Spider-Man: No Way Home” y se estrena en Diciembre de 2021. Esto si todo sale como planeado… ¿Se acuerdan cuando sabíamos qué películas se estrenaban en el año y podíamos hasta escribir posts con títulos como Películas para ver en 2018 o Películas para ver en 2019? La última vez que fui al cine fue en Noviembre de 2019, y no me veo yendo de nuevo hasta dentro de unos cuantos meses…

Volviendo a Spider-Man, uno de mis personajes favoritos de los cómics, la presentación del título se hizo con un video que termina con la cámara apuntando a un pizarrón con el título:

Spider-Man: No Way Home

Spider-Man: No Way Home – clic para ver más grande

Conociendo a Marvel, estoy seguro que después de ver la película vamos a encontrar varias pistas de la trama o personajes y demás en ese pizarrón, así como en los títulos “falsos” que publicó parte del elenco previo al anuncio oficial.

📺 Este viernes es el último episodio de la primera temporada de Wandavision. La serie me resultó un poco lenta al principio, pero se fue poniendo bastante buena y ya los últimos dos capítulos estuvieron excelentes. Me intriga ver cómo termina, va a ser un capítulo bastante largo seguramente. El viernes de la semana que viene no hay capítulo nuevo de serie de Marvel, pero el viernes 19 se estrena Falcon and Winter Soldier, y eso tiene pinta que va a estar tremendo.

📻 Creo que esta noticia la escuché en Olor a Geek, el programa de radio de Bruno Conti y Nacho Alcuri que se transmite todos los sábados por FM en Montevideo, Twitch, la página web y Spotify (pero acá no posteamos links a Spotify porque condenamos el uso de ese servicio). La noticia es que se estaría escribiendo o produciendo District 10, la secuela de la excelente película de ciencia ficción District 9. Como lo escuché por arriba, no busqué mucho más información. Pero me deja contento que haya secuela porque la original está muy buena. Sin embargo comparto esos enlaces del programa de radio para difundir una actividad más para pasar el rato un sábado de tarde.

Picando Código

He hecho algo de limpieza por acá y por allá en el blog. Con los días libres que voy a tener este mes seguro le dedique un poco más de tiempo a algunas cosas que quiero hacer desde hace un tiempo. Con suerte también escriba más posts, aunque en febrero comprobé que los 7 días en el Picandoverso me motivan a escribir más. Febrero fue el mes con más posts en un mismo mes en años (y eso que es un mes más corto). Pueden ver los posts por mes y año en el menú de “Archivo” en la barra derecha del blog.

También ayudó que me colgué a escribir bastante sobre Street Fighter, y todavía queda más por escribir. Si bien llamé Febrero el mes de Street Fighter, he perdido toda la percepción del tiempo desde que estoy en aislamiento, así que justifico por ahí seguir jugando y escribiendo sobre Street Fighter 😆

Si leíste hasta acá, gracias. No he recibido mucho feedback sobre éste tipo de post, así que no tengo mucha idea si alguien llega a leer todas las pavadas que escribo. Pero cualquier comentario es bienvenido.

Podés seguir los posts de Picando Código por RSS, Twitter (cuenta únicamente con posts del blog) y canal de Telegram. También estoy en Twitter y Mastodon, donde además de compartir lo que se publica en el blog publico alguna cosa más.

Otros 7 días en el Picandoverso:

El post Siete días en el Picandoverso – Primera semana de Marzo fue publicado originalmente en Picando Código.

» Leer más, comentarios, etc...

Picando Código

Street Fighter: La película

marzo 03, 2021 11:00

Street Fighter es una película de acción de 1994 basada en la serie de videojuegos de Street Fighter. Fue producida por Capcom y desde el arranque tuvo varios problemas de producción. Igual terminó siendo un éxito comercial para Capcom, no así con la crítica y los seguidores de la saga en su momento.

Dale a Hollywood cualquier temática con acción, y te saca una película militar. El guión se alejó del origen en las artes marciales del videojuego, centrándose más en una guerra en un país inventado de Asia. En este caso Shadaloo es el nombre del país -en vez de la organización criminal de M. Bison– donde estalló la guerra entre el dictador y las fuerzas aliadas.

Gran parte del cambio de tono hacia algo más miliquero tuvo que ver con la participación de Jean-Claude Van Damme. Capcom lo tenía en la mira para interpretar a Guile, y fue de los primeros actores confirmados (y el que se llevó la tajada más grande del presupuesto). Al ser la estrella principal, la historia tuvo que girar más entorno a su personaje, que en el videojuego es un piloto de la armada.

Otro factor que afectó el tono del guión fue un acuerdo por las figuras de acción entre Capcom y Hasbro. La venta de juguetes y Hollywood siempre de la mano. Los juguetes iban a ser del estilo G.I. Joe, por lo que tener figuras a lo G.I. Joe le daba sentido a hacer la película más orientada hacia la guerra.

La otra estrella en confirmarse para la película fue Raúl Juliá, quien aceptó el papel porque sus hijos eran grandes fans de Street Fighter. Su interpretación del dictador malvado M.Bison es brillante, y fue de las pocas cosas que la crítica alagó.

Entre los problemas de producción, el libro Street Fighter Undisputed comenta que no había una dirección del proceso creativo para emparejarlo con la visión de los creadores y diseñadores japoneses. No había coherencia y cada quien inventaba en el momento. Encima de eso, Van Damme estaba en pleno enredo amoroso con Kylie Minogue, actriz australiana que interpretó a Cammy.

Raúl Julia estaba sufriendo de cáncer del estómago durante la filmación, y falleció dos meses antes del estreno. La película está dedicada a su nombre y fue su última producción cinematográfica.

[See image gallery at picandocodigo.net] Hay que entender esta película como un producto de su época. En los 90’s, ser fiel al material original no era un requisito excluyente. Hoy en día -sin duda en parte gracias al trabajo de Marvel Studios con su universo cinematográfico- tenemos interpretaciones más fieles y respetuosas del material de donde se origina las películas (como Street Fighter: Assassin’s Fist, por ejemplo). Pero recordemos siempre que se trata de distintas interpretaciones. También gracias a consumir tantos cómics, ciencia ficción y demás, entendemos que cada interpretación puede convivir con las demás en este universo o estos multiversos en el/los que vivimos.

Esto para comentar un poco algunas de las libertades artísticas que se toma: Chun-Li es reportera en vez de agente de Interpol, aunque su motivación por matar a Bison se mantiene. Charlie sigue siendo un amigo de Guile, pero en la película se llama Carlos Blanka y termina siendo convertido en un mutante verde por el científico rehén de Bison: Dhalsim. E. Honda es sumo pero en vez de Japón, viene de Hawaii. Balrog no está del lado de los malos sino que trabaja con Chun-Li. Vega está relativamente bien representado, transmitieron bien que me resulta tan odioso como en el videojuego. T. Hawk es un soldado más del montón, Sagat es vendedor de armas, Ryu y Ken son estafadores baratos, y así…

Guile diciéndonos cuánto le importa ser fiel al material original

Guile diciéndonos cuánto le importa ser fiel al material original

Por lo menos los personajes se basan levemente en el material original. Y los atuendos en general no están tan mal. Tanto gracias a los actores elegidos como a detalles en la forma de vestirse, identificamos quiénes son. Por lo menos la mayoría, T. Hawk no estaba convencido si era él hasta que Guile lo llama por nombre. Está bueno que distintos actores tengan distintos acentos -como mencioné en la reseña de Street Fighter Alpha: The Movie– como para darle el toque internacional que merece la saga. Acá justo Van Damme tiene bruto acento Franco-Belga y hace de americano, pero bueno, apenas un detalle…

El humor es muy particular, pero hay algunos remates y chistes que son buenos. Hay diálogos que son muy citables y graciosos. Particularmente Dee Jay y Zangief tienen un par de escenas cómicas que están muy buenas. Por ejemplo esta de Zangief, y esta otra de Dee Jay que está tremenda, el actor hace que el chiste funcione perfecto con la reacción:
Street FIghter - Okay

De repente una crítica que tuvo es que se esperaba que fuera más seria, pero hasta el desarrollo de Street Fighter 2 tuvo su humor. A pesar de eso, cuenta con varias escenas de acción y pelea. A lo que se va desarrollando creo que vemos más “guerra” que artes marciales, pero en el desenlace final sale mucho Street Fighter. Vemos varias peleas entre protagonistas de la serie y reconocemos movimientos característicos del videojuego. Las escenas de artes marciales no están mal. Así que dentro de todo tampoco es tan alejada del material original, a pesar de las tantas variaciones.

Recordaba la película como mala, y todo lo que había leído al respecto este tiempo hizo que la mirara con expectativas muy particulares. Esperaba ver una película ridícula, aburrida y que a lo mejor me hiciera odiar Street Fighter. Pero viéndola como lo que es, y con expectativas medidas, la disfruté. Es entretenida y tiene suficiente de Street Fighter como para considerarla al momento de mirar películas de la saga. Así que a modo de conclusión diría que tomando en cuenta el contexto, es una película entretenida y disfrutable. Valió la pena darle otra oportunidad y espero que más gente le vuelva a dar la oportunidad que se merece.

Street Fighter: El videojuego de la película del videojuego

En paralelo con la película, Capcom llevó un estudio de desarrollo de videojuegos a Australia a hacer motion capture de los actores para desarrollar un videojuego de peleas al estilo Mortal Kombat con los personajes de la película. Les costó bastante poder tomar capturas con todos los actores, que estaban pidiendo más plata por esto, y al final fue otra producción destinada a fracasar. El resultado final es desastroso. No le voy a dedicar más palabras que este párrafo, porque ni eso se merece, más vale seguir jugando a Street Fighter II.

El post Street Fighter: La película fue publicado originalmente en Picando Código.

» Leer más, comentarios, etc...

Picando Código

GNOME Latam 2021 – Conferencia online para América Latina

marzo 02, 2021 12:00

GNOME Latam 2021

GNOME anunció GNOME Latam, un día para celebrar y expandir la comunidad GNOME en América Latina. El llamado a presentaciones está abierto y están buscando oradores que presenten en Español y Portugués. El anuncio:

Ven a conocer y compartir experiencias en la creación y uso de las tecnologías GNOME en nuestra región.

Acerca de GNOME Latam

El proyecto GNOME Latam 2021 es una agrupación de miembros de GNOME, este proyecto apunta en especial a todas aquellas personas de la comunidad hispana que se sientan atraídas y motivadas en participar ya sea con o sin conocimientos informáticos con la intención es poder difundir Novedades sobre Avances Recientes en la Comunidad y su Tecnología en el desarrollo de Software Libre, en especial en proyectos relacionados con el ambiente GNOME.

Acerca de GNOME

GNOME es un proyecto de entorno de software gratuito y de código abierto respaldado por una fundación sin fines de lucro. Juntos, la comunidad de contribuyentes y la Fundación crean una plataforma informática y un ecosistema de software, compuesto enteramente de software libre, diseñado para ser elegante, eficiente y fácil de usar.

Nota: debido al COVID-19, GNOME Latam será realizado online.
Empieza: 27 de marzo 2021 a las 13:00 UTC
Finaliza: 27 de marzo 2021 a las 22:00 UTC

Online: https://meet.gnome.org/b/kri-tl1-pdi-ynk

La convocatoria para enviar resúmenes para ponencias está abierta, podemos enviarlas en este enlace. Y ya nos podemos inscribir en este enlace.

El post GNOME Latam 2021 – Conferencia online para América Latina fue publicado originalmente en Picando Código.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 433

marzo 02, 2021 07:05

Enlaces interesantes

Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Picando Código

Enlaces sobre Ruby Febrero 2021

marzo 01, 2021 12:00

RubyA raíz de la saga de posts 7 días en el Picandoverso, he ido recolectando y publicando varios enlaces sobre Ruby y cosas relacionadas al lenguaje de programación diseñado para la felicidad de quienes lo programan. Acá están compilados los enlaces Ruby de esos posts y alguna cosa más 🙌🏻

Los posts de “Siete días en el Picandoverso” se publican todos los miércoles y siempre agrego algún enlace de Ruby. Para estar al tanto, podés seguir los posts de Picando Código por RSS, Twitter (cuenta únicamente con posts del blog) y canal de Telegram. También estoy en Twitter y Mastodon, donde además de compartir lo que se publica en el blog publico alguna cosa más.

El post Enlaces sobre Ruby Febrero 2021 fue publicado originalmente en Picando Código.

» Leer más, comentarios, etc...

Una sinfonía en C#

Observable en Javascript y RxJs, ¿Qué son?

marzo 01, 2021 12:00

Muchas acciones en una aplicación ocurren de manera asincrónica, es decir, no nos quedamos a la espera de un dato sino que éste llega en un momento aleatorio. Un ejemplo claro es cuando un usuario presiona un botón en nuestra aplicación, nuestro código no espera que el usuario presione el botón porque ocurrirá en un momento aleatorio o tal vez nunca ocurra, es decir, es asincrónico; nuestro código no está sincronizado o atado a la acción para continuar con lo que tiene que hacer, sino que la acción llega y el código hace algo en consecuencia en un momento cualquiera.

Esto es aplicable para casi cualquier tecnología de programación y se hace muy evidente en Javascript con las llamadas a API donde invocamos una URL pero la repuesta puede tardar un tiempo indefinido en llegar (o no llegar nunca, o fallar).

Siempre han habido métodos para manejar este tipo de escenarios, como callbacks, promesas, etc. Hace unos años que existe el conceptos de observables aplicado a este tipo de casos. Existen varias bibliotecas para trabajar con observables y probablemente RxJs sea la más conocida.

En este post vamos a hablar sobre observables y sobre RxJs

Como siempre vamos a simplificar algunos conceptos, obviar ciertos detalles y explicar desde el punto de vista de Javascript y RxJS para no extendernos tanto.

¿Qué es un observable?

Un observable es un tipo de objeto que sirve para manipular un stream de datos.

Y ¿Qué es un stream de datos?

Es uno o más datos que llegan de manera asincrónica, por ejemplo, si miramos desde arriba una autopista veremos pasar coches en una tasa variable, podemos decir que es un stream de coches.

En una aplicación web la respuesta de una API se considera un stream de datos, es decir, llegarán datos en un momento indeterminado y, en el agún momento, se detendrán o no.

Otro ejemplo podría ser los eventos del movimiento del mouse sobre la pantalla, son datos que llegan asincrónicamente y a una tasa variable, por lo tanto es un stream de datos.

Entonces

Como dijimos, un observable es un objeto que permite manipular una stream de datos, en RxJs tiene varios métodos pero nos vamos a centrar en uno, susbscribe y en particular en esta sobrecarga:

  subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): Subscription;

Como vemos recibe tres parámetros opcionales y retorna un objeto Suscription el cual de momento vamos a ignorar.

Primer ejemplo

Para comenzar a aprender a usar observables el primer problema es ¿dónde conseguimos un observable?

Bien, la librería RxJs tiene varios métodos para crear observables a partir de diferentes fuentes:

  • Arrays
  • Promesas
  • Objetos
  • Eventos

Entonces, como primer experimento vamos a crear un observable de un array, suscribirnos y analizar qué ocurre.

Crear un observable a partir de un array

let obs = from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);

De esta manera y gracias al método from creamos en la variable obs un observable de que nos devolverá un stream de números desde el 0 al 9.

Importante: los observables no harán nada hasta que nos suscribamos a ellos

let subs = obs.subscribe((item)=>{
    console.log(item);
});

Éste es el modo más simple de suscribirnos y “activar” el observable, en este caso solo pasamos un parámetro al método subscribe que se llama next que es un función que sea invocada cada vez que hay un nuevo elemento disponible en el stream, en nuestro caso se invocará 10 veces y finalizará ya que el array que hemos utilizado para crear el observable solo tiene 10 elementos, si ejecutamos el código vemos lo siguiente en la consola

El objeto Observer en detalle

En la sobrecarga del método subscribe hemos visto que nos pide tres parámetros, todos opcionales, o un objeto observer, qué es un objeto con tres métodos y sería equivalente a esto:

const observer = {
    next: (item: any) => {
        console.log(item);
    },
    complete: () => {
        console.log("complete");
    },
    error: () => {
        console.log("error");
    }
}

Simplemente un objeto con tres métodos, next, complete y error, estos métodos tienen una especial importancia al interactuar con un observable y es la siguiente

  • Next: se invoca con cada nuevo valor que entrega el stream
  • Complete: se invoca solo una vez, cuando ya no hay valores que entregar, es decir no recibiremos otro next, nótese que puede ser que esto nunca ocurra, por ejemplo si estamos escuchando un evento que puede ocurrir infinitamente como el movimiento del mouse.
  • Error: solo se invoca si hay un error y ejecuta complete automáticamente a continuación, el observable da error y finaliza. Por supuesto que en condiciones normales esto puede no ocurrir nunca.

Volviendo al ejemplo, si ahora en lugar de pasar solo una función al método subscribe pasamos un objeto observer así:

const observer = {
    next: (item: any) => {
        console.log(item);
    },
    complete: () => {
        console.log("complete");
    },
    error: () => {
        console.log("error");
    }
}

let obs = from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
let subs = obs.subscribe(observer);

Vemos que ocurre algo ligeramente diferente, y es que después de entregarnos el valor 9 a través de next se invoca a complete, ya que nuestro stream finaliza y por lo tanto lo hace el observable.

Se dice que tenemos un observer asociado a ese observable, por supuesto podemos asociar cualquier cantidad de observers a un observable.

Creando observables a partir de diferentes orígenes

Crear un observable a partir de un evento del DOM

// fromEvent(element, eventname);
const buttonClickObs = fromEvent(document, "click");
buttonClickObs.subscribe(observer);

En este caso el método fromEvent nos permite crear un observable a partir de cualquier evento así es que al hacer click sobre la página que estamos veremos un nuevo evento, nótese que este observable nunca finaliza

Crear un observable a partir un objeto cualquiera

const person = {
    name : "Leonardo"
};
const personObs : Observable = of(person);
personObs.subscribe(observer);

En este caso tenemos un único elemento en el stream (el mismo objeto) y finaliza.

Crear un observable a partir de un timer

interval(1000).subscribe(observer);

En este caso veremos un entero llegar cada uno segundo (1000ms) infinitamente que va a ir incrementando su valor.

Existen otros métodos para crear observables que iremos viendo más adelante cuando toquemos otros temas, de momento lo dejamos acá.

Nos leemos.

» Leer más, comentarios, etc...

Una sinfonía en C#

Nuevo blog! (otra vez)

febrero 28, 2021 12:00

Hola a todos!

Por tercera vez en la larga historia de este blog cambiamos de plataforma, en este caso github pages por facilidad y poder publicar desde cualquier editor de texto.

La idea es seguir publicando con frecuencia random (como siempre). Para los curiosos dejo los links a los antiguos blogs:

“Viejos blogs”

Con el tiempo intentaré mejorar un poco el look&feel aunque lo más importante es el contenido.

Nos leemos.

» Leer más, comentarios, etc...

Blog Bitix

Cifrado y descifrado como servicio con Vault

febrero 26, 2021 03:00

Implementar la seguridad en una aplicación no es sencillo, cuando un sistema se compone de múltiples aplicaciones los posibles fallos de seguridad se multiplican. Vault es una herramienta que permite centralizar y delegar varios aspectos de las aplicaciones relativos a la seguridad, uno de ellos es el cifrado y descifrado de los datos para su almacenamiento y recuperación de una base de datos. Entre sus funcionalidades Vault ofrece como servicio el cifrado y descifrado de datos.

HashiCorp Vault

HashiCorp

La seguridad en una aplicación involucra a los datos, tanto en la transmisión entre aplicaciones como en el almacenamiento a ambos se les conoce por la seguridad de los datos en tránsito o transit y por la seguridad en persistencia o rest. La seguridad de los datos en tránsito se consigue utilizando protocolos de comunicación seguros como TLS y la seguridad de los datos en persistencia se consigue cifrando los datos ya sea a nivel de sistema de archivos, base de datos o datos específicos aplicando un algoritmo de cifrado.

No es fácil implementar la seguridad, por un lado hay que utilizar algoritmos de cifrado considerados seguros, en un sistema grande en el que hay múltiples aplicaciones que utilizan potencialmente diferentes lenguajes de programación y librerías han de tener soporte para esos algoritmos de cifrado. Por otro lado, al tener múltiples aplicaciones requiere que cada una de ellas mantenga seguras las claves privadas en las que se fundamenta la seguridad de cifrado y descifrado, con múltiples aplicaciones los posibles puntos vulnerables son varios.

La seguridad de los datos es muy importante, ciertos datos personales sensibles y que permiten identificar a personas están protegidos por leyes. El no cumplimiento de las leyes implica potencialmente a una empresa recibir importantes multas o descrédito que afecte a la viabilidad del negocio o suponga una reducción de facturación. Algunos datos candidatos a ser cifrados o transformados al guardarse en base de datos son, datos personales e identificativos como nombre y apellidos, DNI, dirección, tarjetas de crédito, bancarios u otros datos que estén regulados por las leyes de protección de datos.

Los datos protegidos incluso no es deseable que sean accesibles por cualesquiera trabajadores de la propia empresa, solo debería tener acceso a ellos aquellos trabajadores que los necesitan para desempeñar su trabajo y ofrecer el servicio que proporciona la empresa. Para el desarrollo de una aplicación los programadores necesitan una base de datos con el mismo esquema de la base de datos de producción y un conjunto de datos, una opción es obtener una copia de la base de datos de producción, sin embargo, obtener una copia de la base de datos de producción otorga acceso a los programadores acceso a los datos, al hacer la copia es posible aplicar un proceso que ofusque los datos sin embargo esto sigue sin solventar el problema de mantener seguros los datos en la propia base de datos de producción o sus réplicas. Si algunos datos se guardan cifrados aunque se tenga acceso a la base de datos los datos cifrados siguen protegidos.

Una de las funcionalidades que ofrece Vault de HashiCorp es ofrecer el cifrado y descifrado como servicio. Este artículo está basado en la guía de Vault sobre la funcionalidad de cifrado y descifrado.

Contenido del artículo

Vault como servicio de cifrado y descifrado

Vault es una herramienta dedicada a la seguridad de la empresa HashiCorp. Tiene diferentes funcionalidades como servir de almacenamiento de secretos en su base de datos de claves y valores, generar credenciales de acceso bajo demanda a recursos como bases de datos entre otras como cifrado y descifrado como servicio. En todas estás funcionalidades diversos aspectos de la seguridad se centralizan en un único componente del sistema.

Las claves de cifrado únicamente se almacenan en Vault, por otro lado las aplicaciones no han de mantener credenciales para el acceso a una base de datos sino que es Vault el que crea las credenciales válidas únicamente por un periodo de tiempo corto con posibilidad de renovación. Esto aumenta la seguridad ya que una aplicación no ha de mantener unas credenciales para la base de datos válidas por un tiempo indefinido, al mismo tiempo las claves de cifrado están centralizadas en vez estar incluidas en cada aplicación. En caso necesario Vault es capaz de revocar las credenciales de cualquier aplicación.

Vault ofrece dos servicios para proteger los datos, el servicio de cifrado y descifrado está disponible en Vault y el de transformación requiere la versión Enterprise.

Servicio de cifrado y descifrado

El servicio de cifrado y descifrado de Vault consisten simplemente en aplicar un algoritmo de cifrado a un dato en texto plano y devolverlo cifrado y realizar la operación contraria aplicar el algoritmo de descifrado a un dato cifrado y devolverlo en texto plano. Además de mantener las claves de cifrado con la posibilidad rotarlas, es decir, crear nuevas claves.

El proceso de cifrado de Vault transforma el dato original en un valor que no tiene ningún sentido sin aplicar el proceso de descifrado. El formato del dato original se pierde, esto es, si el dato original es un número de teléfono con el formato (+34) 666554433 el dato cifrado es una secuencia de caracteres de cierta longitud con otro formato. Esta pérdida de formato es un inconveniente al guardar el dato en la base de datos para solventarlo Vault ofrece el servicio de transformación.

Uso del servicio de cifrado y descifrado de Vault

Uso del servicio de cifrado y descifrado de Vault

Servicio de transformación

En vez de cifrado Vault también ofrece un servicio de transformación que permite obtener un dato ofuscado pero que conserva el mismo formato y longitud que el original. Que el dato tenga el formato original es importante en una base de datos relacional ya que la longitud y formato de la columna para guardarlo será el mismo que el original, en el caso de un dato cifrado el campo se ha ade adaptar al resultado cifrado lo que no es deseable.

1
2
$ vault secrets enable transform
...
vault-transform.sh

En este ejemplo se codifica y descodifica un número de tarjeta de crédito conservando el formato.

1
2
3
4
$ vault write transform/encode/payments value=1111-2222-3333-4444
Key              Value
---              -----
encoded_value    9300-3376-4943-8903
vault-transform-encode.sh
1
2
3
4
$ vault write transform/decode/payments value=9300-3376-4943-8903
Key              Value
---              -----
decoded_value    1111-2222-3333-4444
vault-transform-decode.sh

Proveedor de claves

En un caso de uso en el que es necesario cifrar volúmenes de datos grandes, como blobs de 1 GB, requiere codificar en base64 y enviar a Vault por red y obtener la respuesta de tal volumen de datos, esto no es deseable para obtener el mejor rendimiento. En vez de enviar los datos se pueden cifrar los datos localmente con la clave obtenida de Vault. La idea es permitir a la aplicación cifrar y descifrar los datos sin necesidad de llamadas y retornos a Vault con grandes volúmenes de datos.

La respuesta para obtener la clave de cifrado contiene la clave de datos tanto en texto plano como en forma cifrada. Con la clave de datos en texto plano se pueden cifrar los datos y almacenar la clave de datos cifrada junto a los datos. Al necesitar descifrar los datos se solicita a Vault descifrar la clave de datos cifrada para obtener la clave de datos en texto plano permitiendo de esta forma descifrar los datos localmente. Esto es, una vez que el blob está cifrado no es necesario almacenar la clave de datos, solo se necesita almacenar la versión cifrada de la misma.

Esta idea permite cifrar y descifrar grandes volúmenes de datos a la aplicación sin realizar comunicaciones de red costosas con Vault. En este caso Vault no proporciona el servicio de cifrado y descifrado sino que lo hace la aplicación, sin embargo, Vault administra la gestión de las claves usadas por la aplicación que no ha de mantener ninguna clave privada.

Ejemplo de cifrado y descifrado de datos

Vault dispone tres métodos de acceso a sus funcionalidades entre ellas el servicio de cifrado y descifrado. Los tres métodos son mediante línea de comandos, mediante API REST o mediante la consola web de administración. En este ejemplo solo se muestra la versión de línea de comandos, la opción mediante API REST es posible probarla mediante una herramienta de linea de comandos como curl.

El primer paso es iniciar Vault, en este caso por simplicidad en modo desarrollo y habilitar el transit engine que proporciona el servicio de cifrado y descifrado.

1
2
$ vault server -dev
$ export VAULT_ADDR='http://127.0.0.1:8200'
vault-start.sh
1
2
$ vault secrets enable transit

vault-enable-transit.sh

El siguiente paso es obtener una clave, Vault la devuelve en texto plano o plaintext y cifrada o ciphertext.

1
2
$ vault write -f transit/keys/app

vault-create-key.sh
1
2
3
4
5
6
$ vault write -f transit/datakey/plaintext/app
Key            Value                                                                                                                                           │
---            -----                                                                                                                                           │
ciphertext     vault:v1:l5Y2HZn+LLq6O5ttSlquXo+x2OMzNH/7ReLpgi47DOWeIGUXmdxHBkk0OtqJe4hqmpyz5QCSx99kwyZu                                                       │
key_version    1                                                                                                                                               │
plaintext      k0n6o+vBAy0g2IFGqoRi5G4t1pMypljY9G4+wWewrEg=
vault-key.sh

Una vez creada la clave, se solicita a Vault cifrar y descifrar datos. Los datos han de proporcionase en codificados en base64.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ vault write transit/encrypt/app plaintext=$(base64 <<< "4111 1111 1111 1111")
Key            Value
---            -----
ciphertext     vault:v1:kYVkH1OxTEai1zjO+uQ9FKiHanlbaQ2bF5b5GYwUiEef5d31ProquZ5grVJfDWrc
key_version    1

$ vault write transit/decrypt/app ciphertext="vault:v1:kYVkH1OxTEai1zjO+uQ9FKiHanlbaQ2bF5b5GYwUiEef5d31ProquZ5grVJfDWrc"
Key          Value
---          -----
plaintext    NDExMSAxMTExIDExMTEgMTExMQo=

$ base64 --decode <<< "NDExMSAxMTExIDExMTEgMTExMQo="
4111 1111 1111 1111
vault-encrypt-decrypt.sh

Algunas aplicaciones para aumentar la seguridad y evitar usar una única clave que en el tiempo quede comprometida Vault proporciona la opción de generar una nueva versión de la misma, la clave antigua sigue siendo válida pero los datos serán cifrados con la última versión. Una vez todos los datos hayan sido cifrados con una versión más reciente las versiones antiguas se pueden deshabilitar.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
$ vault write -f transit/keys/app/rotate
$ vault write transit/encrypt/app plaintext=$(base64 <<< "4111 1111 1111 1111")
Key            Value
---            -----
ciphertext     vault:v2:aVuvvlcyPX5J4sPYXFWHL53sIDx3HP9oBBTNjhY6NyshliMZzw8g8Ir9+BRpI8FJ
key_version    2

$ vault write transit/encrypt/app plaintext=$(base64 <<< "4111 1111 1111 1111")
Key            Value
---            -----
ciphertext     vault:v2:8coGwMB2WZsWb8Ogm4Fi8zGgzJq45V+VgYXYaMLHoVSCv9IJXs7Js6Jp5bqDGTUV
key_version    2

$ vault write transit/decrypt/app ciphertext="vault:v2:8coGwMB2WZsWb8Ogm4Fi8zGgzJq45V+VgYXYaMLHoVSCv9IJXs7Js6Jp5bqDGTUV"
Key          Value
---          -----
plaintext    NDExMSAxMTExIDExMTEgMTExMQo=

$ base64 --decode <<< "NDExMSAxMTExIDExMTEgMTExMQo="
4111 1111 1111 1111
vault-rotate-key.sh

Ejemplo aplicación con Spring

Spring proporciona clases de soporte para el acceso al servicio de Vault. Tanto para la configuración de acceso a Vault como para usar sus servicios mediante una API de clases Java sin tener que recurrir a la API REST de Vault directamente. La clase VaultOperations contiene las referencias de clases para el acceso a las API de Vault, para el caso de el servicio de cifrado y descifrado con la clase VaultTransitOperations.

Uso del servicio de cifrado y descifrado de Vault en una aplicación Java

Uso del servicio de cifrado y descifrado de Vault en una aplicación Java

Para usar el servicio de cifrado y descifrado en una aplicación de Spring, Vault permite la autenticación mediante el mecanismo AppRole. AppRole es un método de autenticación destinadas a las aplicaciones, básicamente proporciona unas credenciales como un usuario y contraseña. El policy se asocia con las credenciales de la aplicación para permitirle el acceso a la clave de cifrado y descifrado app.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$ vault auth enable approle

$ vault policy write app -<<EOF
path "transit/encrypt/app" {
   capabilities = [ "update" ]
}
path "transit/decrypt/app" {
   capabilities = [ "update" ]
}
EOF

$ vault write auth/approle/role/app \
    secret_id_ttl=10m \
    token_num_uses=10 \
    token_ttl=20m \
    token_max_ttl=30m \
    secret_id_num_uses=40 \
    policies=app
app-vault-approle.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ vault read auth/approle/role/app/role-id
Key        Value
---        -----
role_id    c0b643d1-9507-dae1-ffbd-6e405e082f1d

$ vault write -f auth/approle/role/app/secret-id
Key                   Value
---                   -----
secret_id             9b1b6fe6-0ee5-4182-c08d-245d20a59351
secret_id_accessor    b9f5af8e-29ee-694c-2751-6f65d5361caf
app-vault-role.sh

Credo el rol app para la aplicación las credenciales formadas por el role_id y secret_id ponen en el archivo de configuración de la aplicación de Spring.

1
2
3
4
5
6
7
8
spring.cloud.vault:
  uri: http://127.0.0.1:8200
  authentication: APPROLE
  app-role:
    role-id: c0b643d1-9507-dae1-ffbd-6e405e082f1d
    secret-id: 9b1b6fe6-0ee5-4182-c08d-245d20a59351
    role: app
    app-role-path: approle
application.yml

El cifrado y descifrado en la aplicación consiste simplemente en hacer uso de la API que proporciona Spring para el acceso a Vault, esta API hace transparente las llamadas REST subyacentes que se hacen a Vault.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package io.github.picodotdev.blogbitix.springcloudvaultcipher;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.vault.core.VaultOperations;

@SpringBootApplication
public class Main implements CommandLineRunner {

    @Autowired
    private VaultOperations vault;

    @Override
    public void run(String... args) {
        String plaintext = "Hello World!";
        String encrypted = vault.opsForTransit().encrypt("app", plaintext);
        String decrypted = vault.opsForTransit().decrypt("app", encrypted);

        System.out.println("Plaintext: " + plaintext);
        System.out.println("Encrypted: " + encrypted);
        System.out.println("Decrypted: " + decrypted);
    }

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Main.class, args);
    }
}
Main.java

El resultado de cifrar y descifrar el dato se muestra como salida en la consola.

1
2
3
Plaintext: Hello World!
Encrypted: vault:v2:arPaLH7cRy221vCTZvNd8csswtmzOc42caVjBQ+T32SW7+x1tLEH0Q==
Decrypted: Hello World!
System.out

En el archivo de dependencias de la aplicación se ha de incluir la que proporciona Spring para añadir el soporte a Vault.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
plugins {
    id 'java'
    id 'application'
}

mainClassName = 'io.github.picodotdev.blogbitix.springcloudvaultcipher.Main'

repositories {
    mavenCentral()
}

configurations {
	all {
		exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
	}
}

dependencies {
    implementation platform('org.springframework.boot:spring-boot-dependencies:2.4.3')
    implementation platform('org.springframework.cloud:spring-cloud-dependencies:2020.0.1')

    implementation('org.springframework.boot:spring-boot-starter')
    implementation('org.springframework.boot:spring-boot-starter-log4j2')
    implementation('org.springframework.cloud:spring-cloud-starter-config')
    implementation('org.springframework.cloud:spring-cloud-starter-vault-config')

    runtimeOnly 'com.fasterxml.jackson.core:jackson-databind:2.12.1'
    runtimeOnly 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.12.1'
}
build.gradle
Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run

» Leer más, comentarios, etc...

Variable not found

Cómo crear bibliotecas de clases o proyectos de consola .NET 5 en lugar de .NET Core 3.1

febrero 23, 2021 07:05

.NET Core

Ya hace tiempo que se lanzó .NET 5, pero seguro que algunos os habéis dado cuenta de que cuando desde Visual Studio creamos determinados tipos de proyecto, como bibliotecas de clases o proyectos de consola, por defecto éstos utilizan como target .NET Core 3.1 en lugar de .NET 5.

No se trata de un error; desde Microsoft justifican esta decisión porque .NET 5 no es una versión LTS, y prefieren que por defecto los proyectos sean creados usando una versión con mayor tiempo de soporte, como .NET Core 3.1.

Esto tiene fácil solución, porque tras crearlo simplemente deberíamos acceder a las propiedades del proyecto o editar el archivo .csproj y modificar ajustar el target framework a nuestro antojo, pero, ¿cómo podríamos evitar tener que hacer esto cada vez?

Opción 1: Crear proyectos usando línea de comandos

Sabemos que desde la línea de comandos de .NET es posible crear proyectos usando la orden dotnet new. Dado que normalmente estaremos utilizando el SDK más reciente, desde ahí podremos crear directamente bibliotecas en .NET 5 sin mayor problema:

C:\MyProjects\Console>dotnet --version
5.0.103

C:\MyProjects\Console>dotnet new console
The template "Console Application" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on C:\MyProjects\Console\Console.csproj...
Determinando los proyectos que se van a restaurar...
Se ha restaurado C:\MyProjects\Console\Console.csproj (en 57 ms).
Restore succeeded.

C:\MyProjects\Console>type Console.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>

C:\MyProjects\Console>_

Opción 2: Cambiar la configuración de Visual Studio

También podemos hacer que Visual Studio muestra plantillas adicionales en el momento de creación de proyectos. De momento esta característica está en preview, aunque aseguran que será la experiencia por defecto en algún momento.

Para activarlo, es necesario acceder al menú Tools>Options, sección Environment, y marcar la casilla "Show all .NET Core templates in the New project dialog (requires restart)":

Mostrar todas las plantillas .NET Core en el diálogo de creación de proyectos

Tras activar la casilla, y una vez reiniciado Visual Studio, al crear proyectos de biblioteca de clases o consola, aparecerá un paso adicional en el asistente para seleccionar la versión de .NET que queremos utilizar:

Selección de versión de .NET

¡Espero que os sea de utilidad!

Publicado en Variable not found.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 432

febrero 22, 2021 07:05

Enlaces interesantes

Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Blog Bitix

Las anotaciones de Java y ejemplo de procesador de anotaciones en tiempo de compilación

febrero 19, 2021 04:00

Las anotaciones añadidas en Java 5 son muy utilizadas por múltiples librerías entre ellas Hibernate, Spring o Immutables. Desde Java 6 se ofrece una API para el procesamiento de las anotaciones en tiempo de compilación que permiten generar archivos de código fuente o emitir mensajes de error. Los procesadores de anotaciones son invocados por el compilador de Java permitiendo extender su comportamiento. En el artículo se muestra una implementación para generar clases que implementan el patrón Builder y otro para realizar comprobaciones.

Java

Las anotaciones fueron añadidas en Java 5 como una forma de enriquecer con información el código. Tienen varios casos de uso, algunas están incorporadas en el propio JDK y las utiliza el compilador como @Override, otras son meramente informativas como @Deprecated, también se utilizan en la generación de documentación Javadoc con taglets, otras se procesan en tiempo de compilación para generar código o archivos dinámicamente al compilar, otras se procesan en tiempo de ejecución. Entre las anotaciones predefinidas incorporadas en el JDK hay algunas más.

En este artículo muestro cómo crear anotaciones para generar errores de compilación propios, como generar código dinámico en tiempo de compilación e integrarlo con un IDE como IntelliJ y con la herramienta de construcción Gradle.

Contenido del artículo

Qué es una anotación en Java

Las anotaciones es una metainformación que se añade en el código fuente. Por sí mismas no hacen nada, es al procesarlas cuando se añade su comportamiento, sirve desde para añadir documentación, realizar comprobaciones de compilación, generar código o programar funcionalidades transversales.

Si se crea directamente una instancia de una clase anotada sin procesar las anotaciones esta no incluye el comportamiento que las anotaciones tienen intención de añadir, es el creador de la instancia el que ha de encargarse de añadirles el comportamiento en el momento de instanciarlas, el procesado de las anotaciones se puede hacer en tiempo de compilación o en tiempo de ejecución.

Este es el código básico de una anotación y su uso en una clase, su definición se realiza con la palabra @interface, se indica a que elementos del código fuente se pueden aplicar y el nivel de retención.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package io.github.picodotdev.blogbitix.annotationprocessor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
public @interface Builder {
}
Builder.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package io.github.picodotdev.blogbitix.annotationprocessor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
public @interface Value {
}
Value.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package io.github.picodotdev.blogbitix.javaannotations;

import java.util.Optional;

import io.github.picodotdev.blogbitix.annotationprocessor.Builder;
import io.github.picodotdev.blogbitix.annotationprocessor.Value;

@Builder
@Value
public class Foo {

    private String name;
    private Optional<String> color;

    public Foo() {
    }

    public Foo(String name, Optional<String> color) {
        this.name = name;
        this.color = color;
    }

    public void sayHello() throws InterruptedException {
        System.out.println("Hello, my name is " + name + " and my favorite color is " + color.orElse("black"));
    }
}
Foo.java

Las anotaciones tienen una sintaxis especial y definen atributos para en el momento de utilización proporcionar valores. Además poseen un nivel de retención según el cual la anotación está disponible:

  • Runtime: la información de las anotaciones quedan disponibles hasta en tiempo de ejecución y accesible mediante reflexión con los métodos de la clase Class.
  • Class: el compilador emite las anotaciones en tiempo de compilación en los archivos class de bytecode pero no están disponibles en tiempo de ejecución. Puede ser útil para herramientas que procesa los archivos de bytecode.
  • Source: las anotaciones son procesadas y descartadas en tiempo de compilación.

Las anotaciones también definen en que elementos del código fuente se pueden indicar:

  • ElementType.ANNOTATION_TYPE se puede aplicar en otra anotación.
  • ElementType.CONSTRUCTOR se puede aplicar en un constructor.
  • ElementType.FIELD se puede aplicar en una propiedad.
  • ElementType.LOCAL_VARIABLE se puede aplicar en una variable local.
  • ElementType.METHOD se puede aplicar en un método.
  • ElementType.PACKAGE se puede aplicar en un paquete.
  • ElementType.PARAMETER se puede aplicar en un parámetro.
  • ElementType.TYPE se puede aplicar en un tipo.

No es habitual tener que crear un procesador de anotaciones, Spring usa de forma extensiva las anotaciones procesándolas en tiempo de ejecución. Otra posibilidad es usar AspectJ para procesarlas procesarlas después de la compilación a bytecode o ByteBuddy que permite procesarlas en tiempo de compilación o ejecución. Otras librerías que usan anotaciones son las librerías Immutables, Lombok e Hibernate.

Procesador de anotaciones

El JDK ofrece una API para el desarrollo de procesadores de anotaciones. Un procesador de anotaciones es una clase que implementa la interfaz Processor, normalmente al crear un procesador de anotaciones se extiende de la clase AbstractProcessor.

Al definir el procesador de anotaciones se indica que anotaciones soporta el procesador y que nivel de código fuente soporta. El compilador de Java al realizar el proceso de compilación invoca a los procesadores de anotaciones proporcionando los elementos de código fuente que los contienen.

El método principal a implementar es el método process, el procesador ha de recopilar la información que necesite a través de los objetos proporcionados en el método y hacer uso de los servicios proporcionados en la clase ProcessingEnvironment. Para generar archivos de código fuente se utiliza el servicio Filer y para emitir mensajes de error el servicio Messager.

Con la infraestructura de servicios de Java se define el procesador de anotaciones creando un archivo de texto en la ubicación META-INF.services/javax.annotation.processing.Processor. El archivo contiene una línea por cada procesador de anotaciones de la librería. Los procesadores de anotaciones también se puede especificar de forma explícita con la opción -processor de javac.

1
2
io.github.picodotdev.blogbitix.annotationprocessor.BuilderProcessor
io.github.picodotdev.blogbitix.annotationprocessor.ValueProcessor
javax.annotation.processing.Processor

Generar código fuente

Utilizando el servicio Filer el procesador de anotaciones es capaz de generar nuevos archivos de código fuente. En este ejemplo se muestra como generar una clase que implementa el patrón Builder para la clase Foo anotada con la anotación @Builder. El procesador de anotaciones explora los elementos de la clase y con las propiedades que descubre genera el código fuente de la clase y los métodos adecuados de la clase Builder. El procesador de anotaciones en este caso emite el resultado mediante un PrintStream.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package io.github.picodotdev.blogbitix.annotationprocessor;

...

public class BuilderProcessor extends AbstractProcessor {

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_11;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Set.of(Builder.class.getName());
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment environment) {
        Set<Element> annotatedElements = new HashSet<>();
        for (TypeElement annotation : annotations) {
            Set<? extends Element> elements = environment.getElementsAnnotatedWith(annotation);
            annotatedElements.addAll(elements);
        }

        for (Element element : annotatedElements) {
            try {
                generateBuilder((TypeElement) element);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return true;
    }

    private void generateBuilder(TypeElement element) throws IOException {
        String name = element.getQualifiedName() + "Builder";
        JavaFileObject javaFileObject = processingEnv.getFiler().createSourceFile(name, element);
        try (PrintWriter pw = new PrintWriter(new OutputStreamWriter(javaFileObject.openOutputStream()))) {
            String packageName = processingEnv.getElementUtils().getPackageOf(element).getQualifiedName().toString();
            String className = element.getSimpleName().toString();
            String builderName = className + "Builder";
            Map<String, String> properties = getTypeProperties(element);
            String propertiesDeclaration = properties.keySet().stream().map(k -> "    private " + properties.get(k) + " " + k + ";").collect(Collectors.joining("\n"));
            String methodsDeclaration = properties.keySet().stream().map(k -> "    public " + builderName + " " + k + "(" + properties.get(k) + " " + k + ") {\n        this." + k + " = " + k + ";\n        return this;\n    }").collect(Collectors.joining("\n"));
            String buildMethod = "    public " + className + " build() {\n        return new " + className + "(" + properties.keySet().stream().collect(Collectors.joining(", ")) + ");\n    }";

            pw.println("package " + packageName + ";");
            pw.println();
            pw.println("public class " + builderName + " {");
            pw.println();
            pw.println(propertiesDeclaration);
            pw.println();
            pw.println(methodsDeclaration);
            pw.println();
            pw.println(buildMethod);
            pw.println("}");
        }
    }

    private Map<String, String> getTypeProperties(TypeElement type) {
        Map<String, String> properties = new LinkedHashMap<>();
        processingEnv.getElementUtils().getAllMembers(type).stream().filter(e -> e.getKind().equals(ElementKind.FIELD)).forEach(e -> {
            properties.put(e.getSimpleName().toString(), e.asType().toString());
        });
        return properties;
    }
}
BuilderProcessor.java

El resultado del procesador de anotaciones usando Gradle es un archivo de código fuente ubicado en build/generated/sources/annotationProcessor/java/main con el siguiente contenido. Las clases del proyecto pueden hacer referencia a esta clase generada como si existiese en el momento de compilación, los IDE también la detectan como cualquier otra clase.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package io.github.picodotdev.blogbitix.javaannotations;

public class FooBuilder {

    private java.lang.String name;
    private java.util.Optional<java.lang.String> color;

    public FooBuilder name(java.lang.String name) {
        this.name = name;
        return this;
    }
    public FooBuilder color(java.util.Optional<java.lang.String> color) {
        this.color = color;
        return this;
    }

    public Foo build() {
        return new Foo(name, color);
    }
}
FooBuilder.java

El uso de la clase builder es igual que cualquier otra clase del proyecto.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package io.github.picodotdev.blogbitix.javaannotations;

import java.util.Optional;

public class Main {

    public static void main(String[] args) throws Exception {
        System.out.println("Hola mundo");
        Foo foo = new FooBuilder().name("foo").color(Optional.of("red")).build();
        foo.sayHello();
    }
}
Main.java

Realizar comprobaciones de compilación

La anotación @Value es una anotación mediante la cual en tiempo de compilación se comprueba que una clase tiene redefinidos en este caso los métodos equals(), hashCode() y toString(). Es importante implementar correctamente los métodos equals(), hashCode() porque son usados por las colecciones, una implementación de estos que no cumple con los contratos de los métodos da lugar a potenciales errores y comportamientos anómalos. En caso de que la clase anotada no tenga redefinidos estos métodos se emite una advertencia de compilación.

Este procesador de anotaciones hace uso del servicio Messenger que posee métodos para emitir mensajes de error, de advertencia o de información. El procesador busca que métodos tiene la clase anotada y si no cumple la validación emite un error.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package io.github.picodotdev.blogbitix.annotationprocessor;

...

public class ValueProcessor extends AbstractProcessor {

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_11;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Set.of(Value.class.getName());
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment environment) {
        Set<Element> annotatedElements = new HashSet<>();
        for (TypeElement annotation : annotations) {
            Set<? extends Element> elements = environment.getElementsAnnotatedWith(annotation);
            annotatedElements.addAll(elements);
        }

        for (Element element : annotatedElements) {
            try {
                List<? extends Element> executableEmentls = element.getEnclosedElements().stream().filter(t -> {
                    return t.getKind().equals(ElementKind.METHOD);
                }).collect(Collectors.toList());
                boolean hasHashCode = executableEmentls.stream().anyMatch(e -> {
                    return e.getSimpleName().toString().equals("hashCode");
                });
                boolean hasEquals = executableEmentls.stream().anyMatch(e -> {
                    return e.getSimpleName().toString().equals("equals");
                });
                boolean hasToString = executableEmentls.stream().anyMatch(e -> {
                    return e.getSimpleName().toString().equals("toString");
                });

                if (!hasHashCode || !hasEquals || !hasToString) {
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Class " + element.getSimpleName() + " should override hashCode, equals and toString methods");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return true;
    }
}
ValueProcessor.java

La clase Foo al estar anotada con la anotación Foo pero no redefinir los métodos equals, hashCode y toString heredados de Object el compilador y el procesador de la anotación genera un mensaje de advertencia en la compilación.

1
2
warning: Class Foo should override hashCode, equals and toString methods

System.out

Procesador de anotaciones en Gradle

Para que Gradle utilice los procesadores de anotaciones definidos en una librería hay que declararlo en la sección de dependencias mediante annotationProcessor.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
plugins {
    id 'java'
    id 'application'
}

repositories {
    mavenCentral()
    mavenLocal()
}

dependencies {
    annotationProcessor 'io.github.picodotdev.blogbitix:annotationprocessor:1.0'

    implementation 'io.github.picodotdev.blogbitix:annotationprocessor:1.0'
}

application {
    group = 'io.github.picodotdev.blogbitix'
    version = '1.0'
    sourceCompatibility = '11'
    mainClass = 'io.github.picodotdev.blogbitix.javaannotations.Main'
}
build-annotationprocessor.gradle

Esta dependencia se instala en el repositorio de Maven local haciendo uso del plugin maven-publish.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
plugins {
    id 'java'
    id 'java-library'
    id 'maven-publish'
}

repositories {
    mavenCentral()
    mavenLocal()
}

dependencies {
}

group = 'io.github.picodotdev.blogbitix'
version = '1.0'
sourceCompatibility = '11'

publishing {
    publications {
        maven(MavenPublication) {
            from components.java
        }
    }
}
build-javaannotations.gradle
Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew annotationprocessor:publishToMavenLocal && ./gradlew main:run

» Leer más, comentarios, etc...

Bitácora de Javier Gutiérrez Chamorro (Guti)

Delphi 26 aniversario

febrero 18, 2021 05:36



Un 14 de febrero, día de San Valentín de 1995 se presentaba Delphi 1.0. Venía a llenar el hueco dejado por Turbo/Borland Pascal 7 y sus respectivas versiones para Windows. En aquellas fechas se oía de Windows 95, el proyecto Chicago que terminaría cambiándolo todo en cuanto a informática de consumo. Su rival iba a …

Delphi 26 aniversario Leer más »



Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

» Leer más, comentarios, etc...

Variable not found

Radzen Blazor Components: ¡ahora open source!

febrero 16, 2021 07:05

Blazor

Es frecuente que alumnos de mi curso de Blazor en CampusMVP me pregunten sobre la existencia de bibliotecas de componentes que les ayuden a desarrollar aplicaciones profesionales más rápidamente, por lo que no podía pasar por alto la noticia que publicaban hace unos días los amigos de Radzen en su cuenta de Twitter:

Los componentes Radzen para Blazor son ahora open source

En efecto, ¡los componentes para Blazor de Radzen han pasado a ser open source y distribuidos bajo licencia MIT!

Para los que no los conozcan, Radzen es uno de los referentes en el mundo de las herramientas y componentes visuales para el desarrollo rápido de aplicaciones web, pero lo que nos ocupa ahora son el conjunto de más de 60 componentes visuales para Blazor Server y WebAssembly que ahora podremos utilizar de forma totalmente gratuita (bueno, aunque existen opciones para pagar por servicios de soporte profesional).

Componentes Radzen para Blazor

¿Qué componentes podemos encontrar ahí?

Radzen es una de las suites de componentes más extensa, por lo que podemos encontrar componentes Blazor prácticamente para todo. Aunque para haceros una idea lo mejor es que acudáis al sitio web de demostración, más o menos se incluye lo siguiente:

  • General
    • Button
    • GoogleMap
    • Gravatar
    • SplitButton
    • Icon
    • Image
    • Link
    • Login
    • ProgressBar
    • Dialog
    • Notification
    • Tooltip
    • Menu
    • PanelMenu
    • ContextMenu
    • ProfileMenu
    • Upload
  • Containers
    • Accordion
    • Card
    • Fieldset
    • Panel
    • Tabs
    • Steps
  • Forms
    • AutoComplete
    • Switch
    • CheckBox
    • CheckBoxList
    • ColorPicker
    • DatePicker
    • DropDown
    • DropDownDataGrid
    • FileInput
    • ListBox
    • Numeric
    • Password
    • RadioButtonList
    • Rating
    • SelectBar
    • Slider
    • TemplateForm
    • TextBox
    • Mask
    • TextArea
  • Validators
    • RequiredValidator
    • LengthValidator
    • NumericRangeValidator
    • CompareValidator
    • EmailValidator
    • RegexValidator
  • Data
    • DataList
    • Pager
    • Tree
    • Scheduler
  • DataGrid
    • Binding to IQueryable
    • Binding with LoadData event
    • Binding to OData service
    • Footer Totals
    • Custom Column FilterTemplate
    • Hierarchy
    • Hierarchy on demand
    • Master/Detail
    • InLine Editing
    • Conditional styles and templates
    • Export to Excel and CSV
    • Cascading DropDowns
  • HTML Editor
    • Default tools
    • Custom tools
  • Charts
    • Line Chart
    • Area Chart
    • Column Chart
    • Bar Chart
    • Pie Chart
    • Donut Chart
  • Gauges
    • Radial Gauge
    • Arc Gauge
    • Styling Gauge

¿Cómo utilizarlos?

Desde el repositorio Radzen Blazor Components de GitHub podríamos descargar el código fuente completo de los componentes e incluirlos directamente en nuestra solución, aunque normalmente lo más sencillo será utilizarlos instalando el paquete NuGet Radzen.Blazor en nuestro proyecto Blazor.

Hecho esto, para mayor comodidad, es interesante incluir en el archivo _Imports.razor general los espacios de nombres donde se incluyen los componentes, así los tendremos siempre a mano:

@using Radzen
@using Radzen.Blazor

A continuación, hay que incluir en la página contenedora del proyecto Blazor, ya sea _Host.cshtml (Blazor Server) o index.html (WebAssembly) las referencias a los scripts utilizados por los componentes Radzen, justo antes del </body> que cierra el cuerpo:

<script src="_content/Radzen.Blazor/Radzen.Blazor.js"></script>

De la misma forma, en esa misma página hay que añadir la hoja de estilos del tema visual utilizar:

<link rel="stylesheet" href="_content/Radzen.Blazor/css/default-base.css">

La versión gratuita de los componentes, además del tema por defecto, incluye tres temas adicionales: dark, humanistic y software, y las versiones de pago ofrecen algunos temas premium más. Para usar alguno de estos temas, lo único que hay que hacer es sustituir el nombre en el <link> anterior, por ejemplo software-base.css.

Algunos componentes, como los cuadros de diálogo, menús contextuales, o tooltips requieren el registro de servicios en el inyector de dependencias. Por tanto, es buena idea dejarlos preparados desde un principio en la clase Startup de Blazor Server o en Program de Blazor WebAssembly:

// Blazor Server:
services.AddScoped<DialogService>();
services.AddScoped<NotificationService>();
services.AddScoped<TooltipService>();
services.AddScoped<ContextMenuService>();

// Blazor Wasm:
builder.Services.AddScoped<DialogService>();
builder.Services.AddScoped<NotificationService>();
builder.Services.AddScoped<TooltipService>();
builder.Services.AddScoped<ContextMenuService>();

¡Y esto es todo! Con estos simples pasos ya tenemos toda la potencia de los componentes Radzen a nuestra disposición 🙂

Publicado en Variable not found.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 431

febrero 15, 2021 07:05

Enlaces interesantes

Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Blog Bitix

Herramientas para convertir texto a audio de voz natural

febrero 13, 2021 11:30

Los archivos de audio son difíciles de editar, un cambio requiere volverlos a grabar de forma parcial o de forma completa lo que requiere mucho tiempo. Hay herramientas que permiten convertir texto a audio de voz sintetizada, el resultado de algunas herramientas es suficientemente bueno como para no distinguirse de una voz humana natural.

Al grabar un audio de voz se crea un archivo binario con la grabación en formato wav o comprimido con mp3. Estos formatos de audio son archivos poco editables, no se puede sustituir una palabra, frase o realizar una corrección. La única edición que permite un audio de voz es cortar un trozo o añadir un trozo de audio nuevo. Sin embargo, la edición no es perfecta y el tono de la voz de la parte editada puede variar con la parte anterior y posterior. Además, grabar un audio de voz requiere el tiempo para realizar la grabación.

Hay herramientas que usando un sintetizador de voz permiten transformar un texto en un audio, dando solución a dos de los problemas de la grabación de voz a partir de texto. Al convertir un texto a voz es más fácil la edición ya que basta simplemente cambiar el texto y generar de nuevo el audio y es más rápido ya que la generación del audio es más rápido que grabarlo en tiempo real. Algunos sintetizadores y herramientas además permiten seleccionar el idioma de la voz o ser una voz masculina o femenina.

Una de estas herramientas es TTS: Text-to-Speech, un proyecto de Mozilla y las ofrecidas por Amazon, Google y Microsoft. Esta y otras herramientas alternativas de conversión de texto a voz producen audios de voz bastante fieles a la voz humana natural. Para ayudar en la interpretación del texto se enriquece con un lenguaje de marcado que indica a la computadora como entonar e interpretar el texto.

Contenido del artículo

Convertir texto a voz natural

Herramientas web

Hay algunas páginas web que ofrecen el servicio de conversión de texto a audio sin necesidad de software adicional a un navegador web. Algunas con limitaciones de número de caracteres pero suficiente para un uso básico. También algunas permiten descargar el archivo de audio, en caso de que no ofrezcan la descargar se puede reproducir y capturar con el reproductor multimedia VLC en la opción Captura de audio > Monitor de audio interno (HDMI) > Guardar.

Servicios de conversión

Para un uso más profesional y avanzado hay algunas aplicaciones para Windows y entre los muchos servicios que ofrecen Amazon AWS, Google y Microsoft para hacer la operación de convertir audio a texto que realmente producen unos resultados muy buenos con una voz natural difícil de distinguir de una real.

Aplicaciones.

Servicios.

Mozilla TTS

La herramienta TTS es capaz de generar un audio de voz sintética a partir de un texto de bastante buena calidad. El audio de voz sintética no es perfecta ni tiene todos los matices en tono, velocidad de habla, contiene algunos defectos y otros matices de las voces humanas pero es aceptablemente bueno.

TTS se ofrece como una imagen de Docker que permite una instalación y uso sencillo de la herramienta. Previamente hay que instalar Docker, una vez instalado descargar la imagen del contenedor TTS e iniciar un contenedor de la herramienta TTS.

Hay varias voces disponibles específicas para cada idioma, la del español se indica con es, la de inglés con en

1
2
$ docker run --rm -it -p 5002:5002 synesthesiam/mozillatts:es

docker.sh

Iniciado el servidor de TTS este ofrece dos interfaces una interfaz web en la dirección http://localhost:5002/ y una interfaz de linea de comandos con una API REST. Tanto en la interfaz web como en la interfaz de linea de comandos se ha de indicar el texto que convertir a audio. Esto genera como resultado un archivo de audio en formato wav. Para que ocupe menos es posible convertirlo a mp3.

1
2
$ curl -o voice.wav -X POST -H 'Content-Type: text/plain' --data '@text.txt' http://localhost:5002/api/tts

curl.sh

Interfaz web de Mozilla TTS

Interfaz web de Mozilla TTS

espeak

espeak y espeak-ng son dos herramientas de linea de comandos que aunque producen una voz sintetizada con un tono fácilmente reconocible como generado por computadora, robótico y metálico, son otras opciones conocidas.

1
2
$ espeak -f text.txt -v europe/es -w voice.wav

espeak.sh

Lenguaje de marcado de síntesis de voz (SSML)

Dependiendo de la herramienta de conversión de texto a voz la calidad de las voces de resultado son más fieles a la voz humana o son fácilmente reconocibles como haber sido generadas por una computadora. Algunas de estas herramientas ya producen conversiones de texto a voz que imitan con fidelidad las voces humanas haciendo uso de redes neuronales entrenadas. Un aspecto por el que son todavía fácilmente identificables es por la entonación y personalidad que los humanos imprimimos en el habla, sin ayuda las conversiones sintéticas de texto a voz son monótonas.

El lenguaje de marcado de síntesis de voz o SSML es un lenguaje que sirve de ayuda a la computadora para interpretar y dar entonación al texto a convertir a voz. Es un lenguaje similar al HTML, con etiquetas atributos y valores, utilizado en las páginas web pero con el propósito de la conversión a voz.

Hay aplicaciones que permite la edición del texto para añadirle el lenguaje de marcado SSML.

Resultado y ejemplo de conversión de texto a voz

El texto que he utilizado para hacer pruebas ha sido el de la descripción de mi blog. Con los siguientes resultados.

1
2
3
4
5
Blog sobre al lenguaje de programación Java y la distribución GNU/Linux que uso habitualmente, Arch Linux,
lo que aprendo sobre el software libre, la programación web y otros temas relacionados con la tecnología y la informática.
El contenido puede contener trazas de asuntos fuera de tema.

Publicando de uno a tres artículos únicos a la semana desde el año 2010.
text.txt

Utilizando el servicio Microsoft Text to Speech.

Utilizando el servicio Free TTS.

Utilizando Mozilla TTS.

Utilizando espeak.

Convertir el audio en formato wav a mp3

En el artículo cómo Cambiar el formato de archivos de música o audio en GNU/Linux comentaba cómo convertir diferentes tipos de audio entre formatos con la herramienta FFmpeg.

El comando para convertir un wav a los formatos mp3 y ogg comprimidos que ocupan significativamente menos sin pérdida de calidad perceptible son los siguientes. En función del formato

1
2
3
4
$ ffmpeg -i voice.wav -codec:a libmp3lame voice.mp3
$ ffmpeg -i voice.wav -codec:a libvorbis voice.ogg
$ ffmpeg -i voice.mp3 -codec:a libvorbis voice.ogg
$ ffmpeg -i voice.mp3 -codec:a libmp3lame -b:a 128k voice.mp3
convert.sh

» Leer más, comentarios, etc...

proyectos Ágiles

20 años de Agile Manifesto. Pasado, presente y futuro

febrero 12, 2021 06:58

Empecé en Agile en el año 2002, utilizando prácticas XP como integración continua, velocidad, burndowns e iteraciones de 2 semanas para que los clientes pudiesen dirigir sus propios proyectos. Recuerdo que me sorprendía cómo esto funcionaba, cómo con un diálogo constructivo sobre opciones y con feedback de usuario final (al que paso a paso nos íbamos acercando cada vez más) construíamos  algo realmente sencillo y útil.

Aquello fue la “primera ola”, que en algunos sitios no ha llegado de entrar y en otros está tomando cada vez más fuerza (fruto de estar cada vez en un mundo más global donde hay que dar servicios digitales a miles o millones de personas), la ola del software hecho con calidad en todos los aspectos.

La segunda ola fue la del trabajo en equipo, la de pensar juntos para ser más creativos y tener mejores soluciones en menos tiempo, colaborando en continuo y no como personas cada uno haciendo su parte. La ola de los equipos multidisciplinares que empezaban a romper silos interdepartementales y a dar una responsabilidad clave al cliente del proyecto.

El presente de la agilidad

La tercera ola es la de la agilidad empresarial, la necesaria para que ese trabajo en equipo realmente luzca y no se quede diluido entre enormes ineficiencias organizativas. Sin embargo, en el estadio actual hay cosas que todavía no se han entendido: modelos de gestión del cambio que obvian la complejidad de los sistemas (por ejemplo planteando hacer formaciones masivas que no van a producir apenas ningún cambio) a intentar “escalar” Agile definiendo procesos corporativos, cosa que se puede quedar en poco más que iterar todos juntos e identificar dependencias cada cuatrimestre. Es un paso inicial que obtiene resultados, pero se queda corto, por dos razones:

1. Una organización grande va a ser inherentemente compleja y nunca suficientemente ágil.

Hay que desescalar, crear organizaciones mucho más pequeñas, con mucha autonomía, en líneas estratégicas pequeñas (Centros de Valor), simplificar permite quitar cosas y llenar esos huecos con inteligencia colectiva. Se trata de hacer spin-offs internos, muy ágiles, que permitan emerger una nueva cultura, con propósitos cercanos a las personas, con ownership para movilizar de verdad la inteligencia colectiva. Se trata de recuperar el espíritu “fundacional” de la empresa.

2. Se “implantan” nuevas prácticas pero se sigue pensando como antes, con lo cual se sigue comportando como antes y obteniendo resultados parecidos a los de antes.

Se continua con estilos de liderazgo basados en “roles duros” (establecer responsables i.e. “culpables” a nivel de individuo), en lugar de objetivos compartidos, centrados en cliente.

En realidad, todo estodel Agile va de resolver problemas (o ir hacia la visión de la empresa). Se trata de juntar las dos cosas: organizaciones pequeñas con objetivos compartidos, por encima de roles. Por suerte, hay quienes han entendido esto, hay lugares donde se trabaja de forma espectacular (porque tienen un leadership espectacular que crea el contexto adecuado). Eso aumenta enormemente su capacidad de supervivencia y éxito futuro como empresa.

En todo este “viaje” de 20 años hay un aspecto fundamental por el cual han pasado mucho de estos líderes y personas que trabajan en entornos Agile: la necesidad de COHERENCIA a nivel personal. Los métodos se aprenden, pero el mindset cuesta mucho cambiarlo, requiere ser consciente. De hecho, durante estos años me he ido dando cuenta de que había que hacer un gran cambio personal para poder trabajar así. He visto a personas (incluido yo) que hemos tenido que trabajar mucho interiormente (y seguimos trabajando en ello) para poder ser consecuentes con lo que decimos, para hacer real todo este juego colaborativo para al que a menudo no venimos preparados (ya desde la infancia nos enseñaron a trabajar nivel individual y todo estaba basado en scorings personales para poder tener mejores oportunidades.

Para hacer esta transformación personal se requiere humildad

Hay que aprender a escuchar, aceptar feedback, tener experiencias (a veces no muy agradables), aprender a colaborar, tener paciencia en el desarrollo de las personas y ser amable cuando se es asertivo y exigente.

A nivel de empresa sucede lo mismo que respecto a cambio personal. La empresa no es un “ente oscuro”, son personas (especialmente de la alta dirección) que toman decisiones. La agilidad empresarial no va a suceder (aunque los equipos lo intenten) si no se crea el mindset adecuado en el liderazgo (que lleve a comportamientos diferentes y así a resultados diferentes). Consecuentemente, hay que trabajar mucho en cambiar modelos mentales, empezando por la alta dirección, pasar de “creencias” a “método científico”: entender cosas como cómo funcionan los sistemas productivos, la motivación humana, cómo dar más autonomía para crear una empresa más potente…

La palabra empowerment se queda corta, se trata de que la gente tenga ownership de lo que hace.

El futuro de la agilidad

El Agile Manifesto habla de sostenibilidad en el ritmo de trabajo, cosa que todavía podemos considerar que está “Work In Progress” en algunos sitios, igual que el uso de prácticas de desarrollo de SW ágiles, que permiten hacer crecer el producto de manera sostenida.

Se nos pide que cada vez tenemos que acelerar más los modelos de Negocio. La pregunta es ¿para qué?

Si lo hacemos sin consciencia, esto nos va a llevar rápidamente hacia un Mundo distópico (¿mad-max?).

El decenio que viene es clave, se nos plantean muchos desafíos: cambio climático, tensiones geo-estratégicas (crisis energética, el posicionamiento de China, modelos democráticos frágiles) y muchas otras cosas. Va a ser un futuro “complejo”, más que complicado.

Como agilistas tenemos responsabilidad sobre el futuro.

Tenemos la responsabilidad de dedicar nuestros esfuerzos sólo a modelos de negocio que sean sanos para la sociedad y respetuosos para el medio ambiente (Agile con consciencia). Tenemos que dar ejemplo en valores y principios para crear un mundo más amable.

En esta línea, en un mundo cada vez más complejo, necesitamos un liderazgo mucho más en clave femenina que masculina, en lugar de ser tan individualistas y competitivos (basándonos en relaciones de poder), tener más en cuenta el contexto y tener más empatía, para colaborar entre nosotros hacia objetivos comunes.

Necesitamos ser Ágiles en acelerar este cambio a todos los niveles: a nivel educativo (en principios y valores, va a ser clave cómo piensen las nuevas generaciones dado que serán quienes moverán la sociedad en los próximos decenios), a nivel personal (en nuestras manos está el escoger a qué modelos de negocio queremos poner al servicio nuestro conocimiento sobre el pensamiento de equipo, el feedback rápido, prácticas, etc.), a nivel de empresas como entes transformadores de la sociedad (las compañías para las que trabajamos, dado que hay modelos de negocio y situaciones que no son “ilegales” pero totalmente faltos de ética), a nivel democrático y a nivel de gobiernos como impulsores y reguladores.

» Leer más, comentarios, etc...

Blog Bitix

Guía de instalación de GNU/Linux para la Raspberry Pi

febrero 12, 2021 03:00

El propósito original del computador de pequeño tamaño Raspberry Pi es el educativo y aprendizaje de progamación e introducción a la electrónica, sin embargo, debido a su bajo coste y ser una computador de propósito general es utilizado con otros múltiples propósitos. El primer paso para empezar a usar una Raspberry Pi es instalarle un sistema operativo, dos opciones son el ofrecido por la fundación de la Raspberry Pi o la versión ofrecida por Ubuntu.

Raspberry Pi

Ubuntu

La evolución tecnológica permite desarrollar componentes electrónicos cada vez más pequeños, más baratos y más capaces que los de la generación anterior. En ejemplo de esto es la Raspberry Pi, una computadora del tamaño de una tarjeta de crédito pero con las características de una computadora de gama baja, aún así suficiente para múltiples propósitos.

Y es que aunque la Raspberry Pi aún no tiene la suficiente potencia como para reemplazar una computadora de escritorio tiene una potencia significativa comparable en procesador y cantidad de memoria a las computadoras de hace una década o lustro. Es seguro que en futuras versiones de la Raspberry Pi será aún más potente y entonces sí quizá para algunos usuarios pase a ser una opción viable para convertirse en el ordenador de escritorio de uso habitual.

La Raspberry Pi 4 es una computadora con las siguientes especificaciones en su cuarta versión. Además, hay diferentes modelos como la Raspberry Pi 400 que está integrada con un teclado.

El propósito primario de la Raspberry Pi no es proporcionar a los usuarios avanzados computadoras de bajo coste, es facilitar la educación en computadoras eliminando la barrera del precio. La primera barrera era el precio, es difícil sino imposible encontrar una computadora con todas las características, de propósito general por menos de lo que cuesta comprar una RPi.

La Raspberry Pi no es barata, hay que tener en cuenta que además hay que comprar el dispositivo de almacenamiento y cargador de energía aparte cuando menos si no hace falta además una carcasa. El modelo con 8 GiB tiene un precio de 85 €. Un NUC de última generación puede equipar mínimo 8 GiB de memoria en los modelos más básicos y hasta 64 GiB en los más capaces siendo sus procesadores significativamente más potentes. Los NUC parten de los 120 € a los que hay que incorporarles la memoria y almacenamiento.

El propósito original de la Raspberry Pi es el educativo y la electrónica, en cualquier caso es capaz de utilizarse para multitud de propósitos como servidor NAS, descargas torrent, centro multimedia, servidor NextCloud como alternativa a las herramientas de Google Docs o Photos o evitar anuncios con AdGuard, consola de juegos retro con RetroPie o Lakka.

Desde su aparición en el año 2010 han surgido numerosas alternativas de la Raspberry Pi en formato y características similares. Algunas incluso más potentes, sin embargo, dada la popularidad de la RPi es la que mejor soporte de software ofrece y de la que hay más información en caso de buscarla.

La revista MagPi en PDF se puede descargar de forma gratuita y contiene numerosos artículos sobre como realizar proyectos de programación y electrónica con esta computadora. También puede adquirirse en formato impreso, se solicita pagar un pequeño importe para mantener la publicación así que si la sueles obtener todos los números considera hacer un pago de vez en cuando.

Contenido del artículo

Cómo instalar GNU/Linux en la Raspberry Pi

Una vez en posesión de una Raspberry Pi y elementos imprescindibles, cargador de alimentación y tarjeta SD o microSD y opcionalmente teclado y monitor, el primer paso es realizar la instalación del sistema operativo. El sistema operativo oficial es Raspberry Pi OS.

La instalación se hace en una tarjeta de memoria microSD o una memoria USB, para la instalación se requiere de otra computadora en la que utilizar la aplicación Raspberry Pi Image que descarga la imagen del sistema operativo y formatea la unidad de almacenamiento. Dispone de una versión para los sistemas operativos Windows, macOS y como aplicación Flatpak para cualquier distribución GNU/Linux.

El grabar la imagen en la tarjeta de almacenamiento conlleva la pérdida de los datos que tuviese con lo es necesario previamente haber hecho una copia de seguridad de los datos que contenga.

Raspberry Pi OS

El sistema operativo Raspberry Pi OS está basado en la distribución Debian, dispone de varias versiones una con entorno gráfico de escritorio y la versión Lite sin entorno de escritorio.

Una de las ventajas de Raspberry Pi OS es que la misma imagen de la distribución sirve par cualesquiera versiones de la Raspberry Pi independientemente de las diferentes versiones que se han lanzado, incluyendo las primeras 1, 2, 3 y 4.

La aplicación Raspberry Pi Image es muy sencilla, basta seleccionar la versión de la Raspberry Pi OS, insertar la tarjeta SD, seleccionar la unidad de la tarjeta SD y proceder a la instalación que se completa en unos pocos minutos. En la tarjeta SD se crean dos particiones, la de arranque o boot y la raíz o root.

Raspberry Pi Imager Raspberry Pi Imager Raspberry Pi Imager

Raspberry Pi Imager

Una vez instalado Raspberry Pi OS basta con insertar la tarjeta SD y conectar el adaptador de corriente. Para comenzar a trabajar con ella antes del inicio se puede conectar a un monitor y un teclado. También es posible acceder a la terminal de la Raspberry Pi desde otro ordenador en la misma red con SSH sin necesidad de conectar a la RPi un teclado y monitor directamente. Para ello es necesario conocer qué dirección IP le ha asignado el router local mediante DHCP.

Raspberry Pi OS root Raspberry Pi OS boot

Archivos de Raspberry Pi OS

Para activar SSH en la Raspberry Pi hay que crean un archivo de nombre ssh en la partición boot. Cuando se inicia la Raspberry Pi lo detecta, activa SSH y lo elimina. El usuario y contraseña por defecto son pi y raspberry respectivamente, es aconsejable eliminar este usuario o cambiar de contraseña.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
[picodotdev@archlinux ~]$ ssh pi@192.168.1.100
pi@192.168.1.100's password: 
Linux raspberrypi 5.10.11+ #1399 Thu Jan 28 12:02:28 GMT 2021 armv6l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sat Feb  6 18:47:28 2021 from 192.168.1.7

SSH is enabled and the default password for the 'pi' user has not been changed.
This is a security risk - please login as the 'pi' user and type 'passwd' to set a new password.

pi@raspberrypi:~ $
ssh-raspberrypio-os.sh
1
2
$ sudo useradd -m -G adm,dialout,cdrom,sudo,audio,video,plugdev,games,users,input,netdev,gpio,i2c,spi picodotdev
$ sudo passwd picodotdev
create-user.sh
1
2
$ sudo passwd pi
$ sudo passwd picodotdev
change-user-password.sh

Una vez con acceso a la Raspberry Pi se pueden instalar los paquetes y programas de software con el gestor de paquetes de las distribuciones basadas en Debian.

1
2
$ sudo apt update
$ sudo apt upgrade
apt-update-upgrade.sh
1
2
$ sudo apt install nginx

apt-install-nginx.sh

Ubuntu Server

Ubuntu también ofrece una versión de su distribución GNU/Linux para la Raspberry Pi para las versiones 2+, no soportando los primeros modelos originales de la Raspberry Pi. La versión de escritorio de Ubuntu para la Raspberry Pi requiere al menos 4 GiB y la versión RPi 4, Ubuntu Server y Ubuntu Core no incluye interfaz gráfica.

El proceso de instalación de Ubuntu tanto para la versión de escritorio como la para las versiones Server y Core, es posible realizarlas mediante Raspberry Pi Image. Raspberry Pi Image permite seleccionar la versión de Ubuntu y esta se encarga de descargarla e instalarla en la tarjeta microSD seleccionada.

Ubuntu para la Raspberry Pi

Ubuntu para la Raspberry Pi

El usuario y contraseña por defecto son ubuntu y ubuntu respectivamente, es aconsejable eliminar este usuario o cambiar de contraseña. Como Ubuntu también es una distribución derivada de Debian el gestor de paquetes y los comandos para instalarlos son los mismos.

1
2
$ sudo apt update
$ sudo apt upgrade
apt-update-upgrade.sh
1
2
$ sudo apt install nginx

apt-install-nginx.sh

Otras distribuciones y documentación

La misma Raspberry Pi Image permite instalar otras distribuciones de uso específico o paquetes como consola de juegos retro, servidor multimedia, servidor NAS, proxy de navegación para evitar publicidad y ser rastreado, nube de documentos personal, … Otras distribuciones también ofrecen una versión de su sistema operativo para la Raspberry como Debian RPi y Arch Linux ARM. Como el sistema operativo se almacena en una tarjeta de memoria o USB es fácil cambiar de una distribución a otra.

Juegos retro para la Raspberry Pi Reproductor multimedia para la Raspberry Pi

Otros sistemas operativos para la Raspberry Pi

Documentación y ayuda:

En otros artículos he comentado como realizar algunos proyectos de electrónica con la Raspberry Pi:

» Leer más, comentarios, etc...

proyectos Ágiles

Organizaciones “Teams-First Thinking”

febrero 09, 2021 05:00

Tener EQUIPOS no es la solución universal a cualquier problema. Pero tiene muchas ventajas que nos hacen optar por este tipo de organización en múltiples ocasiones.

¿Cuáles son estas situaciones? Las generadas por problemas complejos, cuando se necesitan múltiples skills, inteligencia colectiva y un approach más ágil. En los campos donde más experiencia tengo, en el desarrollo de software y el baloncesto, he tenido evidencias de que es la solución más óptima.

Un tipo de organización para cada problema

Necesitamos identificar el problema a solucionar para poder responder al tipo de organización que necesitamos. En el libro Game Plans, Robert W. Keidel nos habla de cómo la diferencia del problema establece también una diferencia de estructura y de estrategia. Algo obvio, pero parece que como sociedad nos hemos atascado en ideas organizativas de principios del siglo XX.

Primeramente, existen problemas no complejos, sino complicados, que requieren de trabajos especializados y aislados o como mucho, en cooperación sin interdependencia.Para este tipo de problemas, los sistemas centrados en “jugadores” especialistas son acertados, estructuras parecidas a la organización en el baseball.

Otros problemas, más rutinarios y repetitivos, necesitan de organizaciones que están más cerca de líneas de producción o máquinas de una fábrica, donde varias piezas tienen que coordinarse, con dependencia pero con poca interacción, para conseguir el objetivo final. Cómo en el fútbol americano con los equipos de ataque, defensa y equipos especiales

Y finalmente existen sistemas con una necesidad de colaboración e interdependencia alta, como en el baloncesto. En estos últimos es donde se asienta el desarrollo de software, ya que trata de resolver problemas complejos (no complicados). Son sistemas “collaboration-driven”

Entendiendo esto, podemos encontrar casos de mal enfoque de la organización respecto al tipo de problema, muchas de las administraciones o empresas de servicios aún tienen su modelo en sistemas productivos del siglo XX (Fordismo). Aunque no desarrollen bienes ni sean una fábrica, siguen organizándose en departamentos especializados en una pequeña parte de su servicio interno.

Si empezamos a pensar de forma sistémica adecuando la organización al problema, podemos obtener resultados asombrosos: El proyecto HOPE reduce en un 80% las esperas de los pacientes oncológicos

Foco en los equipos para conseguir organizaciones “collaboration-driven”.

Trabajar con este tipo de sistemas implica mucha energía. Y si quieres obtener todos sus beneficios, necesitas de una estructura que su foco organizativo sean los equipos y la estructura gire alrededor de ellos, una organización “Team First Thinking”. Para esto entiendo 4 puntos claves donde enfocar los esfuerzos.

1. Management como verdaderos líderes de equipos

No coordinadores, no “thinkers”, no líderes de departamentos especialistas, sino verdaderos líderes enfocados en el EQUIPO.

Fijémonos en los grandes entrenadores de equipos deportivos, entienden de su deporte, de técnica, táctica, estrategia, preparación física, comunicación, gestión de grupos… Son conocedores del sistema al completo, y no son los máximos expertos en cada campo, pero son capaces de conseguir que el entorno sea el adecuado para el desarrollo del equipo.

Los verdaderos líderes de EQUIPOS son líderes generalistas.

Guardiola, Mourinho, Phil Jackson, Greg Popovich, Steve Kerr. Cada uno con su estilo, pero todos tienen esto en común. Conocen bien el sistema al completo y trabajan en la mejora del mismo. Piensan y actúan estratégicamente tanto para el desarrollo individual de cada miembro como para desarrollar, cambiar o provocar el cambio en el entorno que rodea al EQUIPO.

2. Una estructura organizativa y estratégica basada en EQUIPOS

Los EQUIPOS no son máquinas de tragar peticiones y devolver resultados (Factorías que consumen tickets del *Escriba aquí su herramienta de gestión más odiada* y producen features). Son una herramienta organizativa para lograr el impacto deseado y resolver problemas complejos.

Ahora mismo muchas empresas, o casi todas, están organizadas en departamentos por especialidad. ¿Qué hace esto? Que las tensiones o necesidades que están intentando resolver estén aisladas, son propias de cada departamento, delegando a los equipos la ejecución de esas estrategias.

Si en un equipo tenemos gente de producto, de diseño, desarrolladores y QAs como especialistas y cada uno su propio departamento, con sus objetivos, tendremos 4 intereses estirando de diferentes individuos en ese grupo. ¡Adiós equipo!

Imaginemos un club deportivo de esta forma. Tendríamos varios equipos, y estructuralmente constaría del departamento de porteros, de defensas, de medios, de delanteros… Cada uno con su estrategia, su táctica y sus entrenamientos separados sin alineamiento, pero se tendrían que juntar 11 de estos diferentes departamentos para jugar un partido o para ganar el campeonato.

Pues no imaginemos más, porque en vuestra empresa seguramente estáis organizados así. Las comunidades por conocimiento o especialidad deben existir, pero como habilitadores para la ejecución de ese conocimiento en un EQUIPO.

El EQUIPO es la unidad mínima organizativa. Los departamentos no deberían ser la base de la estructura, sino un tipo de organización soporte para los EQUIPOS.

3. Ceder la responsabilidad y el ownership a los equipos

Actualmente cedemos la responsabilidad del diseño del producto al departamento de producto, la del desarrollo al departamento de desarrollo, la responsabilidad de que no nos metan goles al departamento de porteros, y la responsabilidad de marcarlos a la de los delanteros. Así suena absurdo ¿verdad?.

Si cedemos la responsabilidad completa, end to end, a un EQUIPO real y centramos la estructura organizativa en dar soporte a las necesidades que tenga este EQUIPO para responder a esa responsabilidad, lograremos realmente marcar la diferencia.

Organizativamente, les estamos dando a los equipos, por defecto, autonomía, un propósito y además espacio para su crecimiento. ¿Os suena de algo?

Usa la estructura organizativa para ayudar a alinear EQUIPOS y no para alienarlos, haciéndoles perder el control de sí mismos.

4. Desarrollar equipos estables como forma de crecimiento.

Crecer no es ser más grandes, sino ser mejores. ¿Necesitamos mejorar nuestro impacto? mejoremos nuestros equipos y mantengamos simple la estructura organizativa. De esta forma podremos escalar de una forma sostenible.

¿Necesitamos producir más “cosas”? Empujar a equipos a producir más sin mejorar su impacto es contraproducente, y nos lleva a soluciones del estilo añadir más “recursos” porque “necesitamos más manos”. En mi experiencia, es el principio del fin.

“Necesitar más manos” tiene su origen en una mala priorización o una pobre gestión de la demanda, salvo en el caso que de verdad necesitemos nuevas formas de impactar en el propósito de la organización. Si es así, nos lleva a no tener que añadir más personas a un equipo, sino crear un nuevo EQUIPO cuyo objetivo sea generar este nuevo impacto.

Esto no quiere decir que vayamos cambiando equipos cada pocos meses como un pool de recursos e ir montando y desmontando grupos de personas como si fuesen engranajes intercambiables.

La necesidad de estabilidad de los equipos para resolver problemas complejos y en sistemas “collaboration-driven” es un tema que da para otro post. Pero básicamente, lograr una colaboración efectiva, ownership y desarrollo de skills, requiere de tiempo para crear confianza, un entorno de seguridad y espacio para el aprendizaje.

¿Necesitas de verdad producir más “cosas”? Desarrolla equipos nuevos, pero no a costa de hacer más complejo el sistema.

La supuesta economía de escala nos ha hecho pensar que agrupar por especialidad y optimizar al máximo esa especialidad es la única manera de poder crecer de forma eficiente económicamente. Esta es la paradoja de la eficiencia, en la que la optimización de los “recursos” en realidad hace que el sistema se vuelva más ineficiente.

Y ojo, porque es posible que para algunas organizaciones, ya les pueda valer crecer a base de economía de escala, eficientando el beneficio económico a partir de la optimización de recursos. Pues les es más sencillo buscar esta vía de reducción de costes que intentar un cambio con un pensamiento más sistémico. Algo que tendría mucho más impacto en su propósito y sería más acertado para la supervivencia de la organización (El proyecto HOPE reduce en un 80% las esperas de los pacientes oncológicos), pero con mayor necesidad de energía y agilidad.

Pero si logramos EQUIPOS que realmente marquen la diferencia y logren un rendimiento e impacto muy por encima de lo esperado, lograremos poder crecer igualmente, pero además lo podremos hacer de forma sostenible.

A partir de crecer con más unidades mínimas organizativas y mejores, creando nuevos EQUIPOS para nuevos objetivos estratégicos, eficientando el flujo de valor y el impacto, simplificamos el sistema. Reducir la complejidad nos permite ser más ágiles y estar listos para adaptarnos a cualquier nuevo problema a resolver que se nos presente.

Abraham Vallez

Post original: https://abrahamvallez.medium.com/organizaciones-teams-first-thinking-96062c4d8009
https://www.linkedin.com/in/abrahamvallez/
https://twitter.com/AbrahamVallez

Para saber más

» Leer más, comentarios, etc...

Mascando Bits

Comprobar permisos de Administrador en una ejecución (estilo 🐍 Python)

febrero 08, 2021 08:00

Habitualmente nos encontramos con la situación en la que debemos ejecutar ciertos programas o flujos de ejecución y necesitamos permisos de Administrador. Estas situaciones se nos presentan cuando tenemos que manejar carpetas y ficheros en acciones que impliquen lectura, escritura o ejecución en directorios protegidos del sistema. Para ello se requiere una elevación de permisos que deberemos comprobar si existe antes de lanzar nuestra ejecución.

Aprovechando que seguramente tengamos que hacer esto varias veces, creo que la mejor opción de hacerlo es con el mejor lenguaje pegamento que existe hasta la fecha, que no es ni más ni menos que Python. Por ello la solución final que vamos a plantear es excelente tanto para administradores de sistemas como para desarrolladores, siendo además multiplataforma GNU/Linux y Windows.

Tenemos que tener en cuenta que GNU/Linux y Windows no funcionan igual para la gestión de permisos. En Windows preguntaremos desde la API disponible si el usuario es administrador, mientras que en GNU/Linux preguntaremos por si el usuario tiene permisos root.

import ctypes, os
from sys import exit


def is_admin():
    is_admin = False
    try:
        is_admin = os.getuid() == 0
    except AttributeError:
        is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0

    print ("Admin privileges: {}".format(is_admin))
    return is_admin

Para mirar los permisos en Windows usaremos la librería ctypes y para los permisos en GNU/Linux usaremos la librería os. Ambas están incluida en Python. Por una razón arbitraría se ha elegido comprobar primero GNU/Linux y después Windows.

Cuando se comprueba en GNU/Linux invocamos a la función os.getuid que nos devolverá 0 en el caso de ser root. Si llamamos a la función desde Windows con Python nos elevará una excepción del tipo AttributeError, que aprovecharemos a capturar para hacer la comprobación en Windows invocando a la función ctypes.windll.shell32.IsUserAnAdmin. En ambos casos guardaremos el booleano de la evaluación y lo devolveremos al final de la función.

Hasta aquí sencillo, pero esto es código Python y es necesario tener el interprete de Python, con el que obliga al sistema a tener instalado el intérprete de Python. Para solucionarlo, deberíamos convertir nuestro código en un programa añadiendo un punto de entrada y devolviendo un código de retorno tras la ejecución.

import ctypes, os
from sys import exit


def is_admin():
    is_admin = False
    is_win = False
    try:
        is_admin = os.getuid() == 0
    except AttributeError:
        is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
        is_win = True

    print ("Admin privileges: {}".format(is_admin))
    return is_admin, is_win

Antes modificaremos un poco la función is_admin()añadiendo un valor is_win que usaremos para saber que estamos comprobando en GNU/Linux o Windows los permisos de administrador, devolviendo el valor como un segundo valor en el retorno de la función.

Recordad que Python es posible recoger múltiples valores de retorno en una variable como una tupla, en variables separadas o incluso tirar los valores que no nos interesen.

def my_function():
    a = 2
    b = 3
    return a, b

a = my_function()
print(a)  # out: (2, 3)

a, b = my_function()
print(a)  # out: 2
print(b)  # out: 3

a, _ = my_function()  #  b es ignorado
print(a)  # out: 2

Además añadimos un punto de entrada debajo de la función is_admin().

if __name__ == "__main__":
    is_admin, is_win = is_admin()
    # Converting boolean to integer 
    ret = int(is_admin == False)
    if is_win:
        exit(ret)
    else:
        exit(ret * -1)

Comprobar si __name__ es __main__, es una excelente forma de hacer que cada unidad de fichero Python sea ejecutable si se quiere. El nombre de __main__ sólo es dado al fichero Python que es dado al intérprete para ser ejecutado y en caso de no serlo, siempre puede ser importado en otro fichero Python, sin que el punto de entrada afecte.

Como vemos, ahora estamos recogiendo en el retorno is_admin e is_win. Necesitamos el segundo valor para decidir el valor de retorno, ya que los códigos de error en Windows son enteros positivos y en GNU/Linux son enteros negativos. El único punto en común es que devolver un 0 es señal de que la ejecución fue correcta. Sabiendo esto debemos adaptar el retorno de los errores dependiendo el sistema.

💾 Binarizando nuestra aplicación Python

Para convertirlo en un ejecutable usaremos PyInstaller, el cual podéis instalar con un simple pip install pyinstaller. Para binarizar nuestra aplicación en un ejecutable autocontenido con el interprete de Python ejecutaremos:

pyinstaller -F is_admin.py

De esta forma resultará, mucho mas sencilla la distribución de nuestra aplicación.

👔 Puesta en Producción

Ahora vamos a plantear los dos casos de uso para Windows y GNU/Linux. Como aclaración, decir que los ejemplos expuestos se pueden usar tanto con la versión binarizada o con la de código invocando al intérprete de Python.

Windows

@echo off

cd
cd /D "%~dp0"
cd

#REM python is_admin.py
is_admin.exe

if errorlevel 1 (
   echo Exit Code is %errorlevel%
   echo.
   echo Admin privileges required
   echo Run it again as Administrator
   echo.
   pause
   exit /b %errorlevel%
)

#REM [your_executable].exe

pause

👀 Puedes descargar la versión binarizada de is_admin.exe

GNU/Linux

#!/bin/bash

pwd
cd `dirname $0`
SCRIPTDIR=`pwd`
pwd

#python is_admin.py
is_admin


if [ $? -eq 0 ]
then
  # [your_executable]
else
  echo Exit Code is $?
  echo.
  echo Admin privileges required
  echo Run it again as Administrator
  echo.
  read -rsp $'Press any key to continue...\n' -n 1 key
  exit $?
fi

read -rsp $'Press any key to continue...\n' -n 1 key

Si no estás tan familiarizado con el Bash te invito a que visites el proyecto ExplainShell para que puedas obtener la explicación de los diferentes comandos y argumentos.

🥅 Conclusiones

La solución presentada, sin ser seguramente perfecta, asegura tanto a administradores de sistemas como desarrolladores, comprobar si existen los permisos de administrador necesarios de manera sencilla; pudiendo hacerlo con una versión binarizada autocontenida que incluya el intérpretete Python, ejecutándolo usando el intérprete de Python del sistema gracias a ser todo dependencias internas, o llamando directamente a la función is_admin() si se quiere integrar en un desarrollo.

El código completo puedes encontrarlo en el siguiente gist de GitHub:

Espero que esta forma de trabajar te resulte útil y puedas extrapolarla y usar para otros casos Python como tu lenguaje pegamento generando código y aplicaciones altamente reutilizables.

» Leer más, comentarios, etc...

Meta-Info

¿Que es?

Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

rss subscripción

Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
planetacodigo

planetacodigo

Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

Idea: Juanjo Navarro

Diseño: Albin