En la solución original de Google Professional Services, API Key Rotation Checker mostraba las API keys que deben rotarse y las que no en dos grupos diferentes.

Al mostrar por pantalla los resultados, ya incluimos un icono para diferenciar las API keys. Ahora vamos a introducir un flag para filtrar las API keys encontradas, no sólo cuando las mostramos por pantalla, sino también cuando las exportamos a cualquiera de los formatos soportados.

Por defecto, se deben mostrar todas las API keys. Sin embargo, vemos que si se muestran por pantalla, podemos distinguir aquellas que deben rotarse de las que no, pero ésto no sucede si exportamos a JSON o CSV.

Empezamos añadiendo dos campos al struct que modela la Key:

// ... before
type Key struct {
    CreateTime  time.Time `json:"create_time,omitempty"`
    DisplayName string    `json:"display_name,omitempty"`
    Name        string    `json:"name,omitempty"`
    ProjectId   string    `json:"project_id,omitempty"`
}
// ... after
type Key struct {
    CreateTime  time.Time `json:"create_time,omitempty"`
    DisplayName string    `json:"display_name,omitempty"`
    Name        string    `json:"name,omitempty"`
    ProjectId   string    `json:"project_id,omitempty"`
    AgeDays     int       `json:"age_days,omitempty"`
    Rotate      bool      `json:"rotate,omitempty"`
}

En la función List, cuando obtenemos la información de la Key, añadimos el valor de AgeDays usando la función que desarrollamos anteriormente daysSinceCreated():

// ... before
    key := &Key{
        Name:        k.Name,
        DisplayName: k.DisplayName,
        CreateTime:  k.CreateTime.AsTime(),
        ProjectId:   projectid,
    }
// ... after
    key := &Key{
        Name:        k.Name,
        DisplayName: k.DisplayName,
        CreateTime:  k.CreateTime.AsTime(),
        ProjectId:   projectid,
        AgeDays:     daysSinceCreated(k.CreateTime.AsTime()),
    }

Nuevo flag --rotate

Si especificamos el flag --rotate, entonces sólo se mostrarán las API keys cuya antigüedad sea mayor que --max-days.

// ... before
    format := fs.String("format", "", "Output format for displaying the API keys")
    if err := fs.Parse(os.Args[1:]); err != nil {
        // ...
// ... after
    format := fs.String("format", "", "Output format for displaying the API keys")
    rotate := fs.Bool("rotate", false, "Display only API keys older than 'max-days'")
    if err := fs.Parse(os.Args[1:]); err != nil {
        // ...

Añadimos el valor a options y el campo en la definición del struct Options:

// ... before
    options := keys.Options{
        MaxDays: *maxDays,
        Redact:  *redact,
        Format:  strings.ToLower(*format),
    }
// ... after
    options := keys.Options{
        MaxDays: *maxDays,
        Redact:  *redact,
        Format:  strings.ToLower(*format),
        Rotate: *rotate,
    }
// ... before
type Options struct {
    MaxDays int
    Redact  bool
    Format  string
}
// ... after
type Options struct {
    MaxDays int
    Redact  bool
    Format  string
    Rotate  bool
}

Filtrando las API keys

Creamos una función llamada Filter en internal/keys/keys.go; la idea es que si se especifica el flag, sólo se deben mostrar las API keys que deben rotarse. Por tanto, Filter recibe un slice de Key, los flags (en format de Options) y devuelve un subconjunto de Key en forma de slice.

Si no se especifica --rotate, significa que debemos mostrar todas las API keys; es decir, no tenemos que filtrar nada:

func Filter(keylist []*Key, options Options) []*Key {
    if !options.Rotate {
        // nothing to filter
        return keylist
    }
// ...

Si tenemos que filtrar, entonces:

// ... before
func Filter(keylist []*Key, options Options) []*Key {
    if !options.Rotate {
        // nothing to filter
        return keylist
    }
    // ... TODO
}
// ... after
func Filter(keylist []*Key, options Options) []*Key {
    if !options.Rotate {
        // nothing to filter
        return keylist
    }
    filteredKeys := []*Key{}
    for _, k := range keylist {
        if k.NeedsToBeRotated(options) {
            k.Rotate = true
            filteredKeys = append(filteredKeys, k)
        }
    }
    return filteredKeys
}

Actualizando Display

Como, cuando filtramos las API keys añadimos la información de si deben rotarse o no, no es necesario volver a calcular si la API key tiene que rotarse o no para mostrar un icono u otro (por pantalla)

// ... before
    for _, k := range keylist {
        if k.NeedsToBeRotated(options) {
            icon = warningIcon
        }
// ... after
    for _, k := range keylist {
        if k.Rotate {
            icon = warningIcon
        }

Finalmente, tenemos que insertar la función Filter entre la obtención de las API keys y que se muestran por pantalla (o exporten a JSON/CSV).

// ... before
    // ...
    // display API keys
    keys.Display(keylist, options)
}
// ... after
    // filter API keys
    keylist = keys.Filter(keylist, options)
    // display API keys
    keys.Display(keylist, options)
}

Si queremos mostrar los dos nuevos campos que hemos introducido para la Key, debemos añadirlos a la función ToCSV():

// ... before
func (k Key) ToCSV(options Options) []string {
    return []string{k.DisplayName, k.Name, k.CreateTime.Format(time.RFC1123), k.ProjectId}
}
// ... after
func (k Key) ToCSV(options Options) []string {
    return []string{k.DisplayName, k.Name, k.CreateTime.Format(time.RFC1123), k.ProjectId, strconv.Itoa(k.AgeDays), strconv.FormatBool(k.Rotate)}
}

Resultado final

Inspirado por el repositorio original, la aplicación comprueba las API keys para revisar si son más antiguas que el máximo de días que recomienda Google antes de rotarlas (90 días). Por defecto, la aplicación busca las API keys en todos los proyectos a los que el usuario tiene acceso, mostrando la lista de claves que encuentra en cada proyecto e identificando las API keys encontradas con un icono (en función de si superan el periodo máximo recomendado para rotas las API keys).

El usuario puede usar el flag --rotate para que la aplicación muestre únicamente las API keys que superan la antigüedad máxima definida por defecto para las API keys. El usuario también puede especificar un periodo máximo diferente a través del flag --max-days.

Además de mostrar las API keys encontradas por pantalla, ésta versión de la aplicación permite formatear la salida en JSON o en CSV.

Serie completa